Skip to content

Files

Latest commit

Oct 4, 2021
cbc1c2e · Oct 4, 2021

History

History
112 lines (90 loc) · 3.45 KB

2021-9-27-gun-for-react-state-management.md

File metadata and controls

112 lines (90 loc) · 3.45 KB
layout title date tags published description image
post
Gun.js: Painless React State Management
2021-09-27 01:37:00 +0000
software
true
React state synchronization and persistence in just a few lines of code.

I feel no pain

Fed up with writing a ton of Redux boilerplate just to handle form input changes?

There’s a better alternative: Gun.js. It makes state synchronization and persistence a breeze.

First, initialize Gun with options that ensure the state is synced with localStorage only (not with peers).

const State = new Gun({
    localStorage: true,
    file: 'State.local', // localStorage key
    multicast: false, // on Node.js, Gun would sync with local area network peers over multicast :)
    peers: [] // this time we don't want to sync with other users
}).get('state');

Then create a React component that uses the state:

class TextInput extends React.Component {
    state = {text:''};
    
    componentDidMount() {
        State.get('text').on(text => this.setState({text}));
    }
    
    onInput(e) {
        State.get('text').put(e.target.value);
    }
    
    render() {
        return (
            <form class="box">
                <input placeholder="Type something"
                       type="text"
                       value={this.state.text}
                       onChange={e => this.onInput(e)} />
            </form>
        );
    }
}

Now you have a text input that syncs its content across component instances and persists it in localStorage.

See the working example on Codepen.

However, if we now unmount the component and write to State.get('text') elsewhere, we'll get the warning: Can't call setState on an unmounted component. This is a no-op, but it indicates a memory leak in your application.

We can avoid that by saving our state subscriptions and unsubscribing them in componentWillUnmount(). Here's a helper class for that:

class BaseComponent extends React.Component {
    subscriptions = {};
    
    subscribe(callback, path) {
        return (value, key, x, subscription, f) => {
            if (this.unmounted) {
                subscription && subscription.off();
                return;
            }
            this.subscriptions[path || key] = subscription;
            callback(value, key, x, subscription, f);
        }
    }
    
    inject(name, path) {
        return this.subscribe((v,k) => {
            const newState = {};
            newState[name || k] = v;
            this.setState(newState);
        }, path);
    }
    
    componentWillUnmount() {
        this.unmounted = true;
        Object.keys(this.subscriptions).forEach(k => {
            const subscription = this.subscriptions[k];
            subscription && subscription.off();
            delete this.subscriptions[k];
        });
    }
}

Now we can extend BaseComponent, which takes care of injecting values to the component's state and unsubscribing on unmount.

class TextInput extends BaseComponent {
    // ...
    componentDidMount() {
        State.get('text').on(this.inject());
    }
    // ...
}

Codepen

Prefer React hooks? Check out this codepen by Carlos.

Upcoming: How to sync your application state with the world.