From ad5e477acc232e0a9723818e3dd7625b0d35751c Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Mon, 27 Sep 2021 15:29:39 +0300 Subject: [PATCH] . --- ...021-9-27-gun-for-react-state-management.md | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/_posts/2021-9-27-gun-for-react-state-management.md b/_posts/2021-9-27-gun-for-react-state-management.md index 567f358..64eeab8 100644 --- a/_posts/2021-9-27-gun-for-react-state-management.md +++ b/_posts/2021-9-27-gun-for-react-state-management.md @@ -12,7 +12,7 @@ image: https://siriusbusiness.fi/assets/images/posts/painless.jpg Fed up with writing a ton of Redux boilerplate just to make a form input editable? -There’s a better alternative: [Gun.js](https://github.com/amark/gun). It makes state synchronization and persistence super easy. +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 make sure the state is synced with localStorage only (not with peers). ```jsx @@ -28,10 +28,8 @@ Then create a React component that uses the state: ```jsx class TextInput extends React.Component { - constructor() { - super(); - this.state = {text:''}; - } + state = {text:''}; + componentDidMount() { State.get('text').on(text => this.setState({text})); } @@ -52,4 +50,56 @@ class TextInput extends React.Component { Now you have a text form that syncs its content across component instances and persists it in localStorage. -See the working example on [Codepen](https://codepen.io/mmalmi/pen/VwWVdKG). \ No newline at end of file +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) \ No newline at end of file