--- layout: post title: "Gun.js: Painless React State Management" date: 2021-9-27 01:37:00 +0000 tags: software published: true description: "React state synchronization and persistence in just a few lines of code." image: https://siriusbusiness.fi/assets/images/posts/painless.jpg ---  Fed up with writing a ton of Redux boilerplate just to handle form input changes? There’s a better alternative: [Gun.js](https://github.com/amark/gun). 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). ```jsx 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: ```jsx 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](https://codepen.io/mmalmi/pen/VwWVdKG). 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: ```jsx 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. ```jsx class TextInput extends BaseComponent { // ... componentDidMount() { State.get('text').on(this.inject()); } // ... } ``` [Codepen](https://codepen.io/mmalmi/pen/MWozPZE) Prefer *React hooks*? Check out this [codepen](https://codepen.io/alterx/pen/VwWqgRV) by [Carlos](https://codepen.io/alterx). Upcoming: How to sync your application state with the world.