I want to demonstrate how you can share any serializable data between React components, e.g. client components in NextJS.
Let’s say we have few unrelated components:
Let's create an object that will contain initial state
export const state: { count: number } = { count: 0 };
We can store data in a WeakMap
, state
will be a key to access it. Also, will need a subscribers
array.
const stateMap = new WeakMap<object, object>();
const subscribers: (() => void)[] = [];
Now let's write a hook to subscribe to data changes:
export function useCommonState<T extends object>(stateObj: T) {
// more efficient than `useEffect` since we don't have any deps
React.useInsertionEffect(() => {
const cb = () => {
const val = stateMap.get(stateObj);
_setState(val!);
};
// subscribe to events
subscribers.push(cb);
return () => {
subscribers.slice(subscribers.indexOf(cb), 1);
};
}, []);
}
Now let's add logic related to get and set state:
// all instances of hook will point to same object reference
const [state, _setState] = React.useState<typeof stateObj>(() => {
const val = stateMap.get(stateObj) as T;
if (!val) {
stateMap.set(stateObj, stateObj)
return stateObj
}
return val
});
const setState = React.useCallback((newVal: object) => {
// update value
stateMap.set(stateObj, newVal);
// notify all hook instances
subscribers.forEach((sub) => sub());
}, []);
return { state, setState };
And now can use it in 3 components like:
import { state as myState } from './state';
//...
const { state, setState } = useCommonState(myState);
<button
onClick={() => setState({ count: state.count + 1 })}
className="p-2 border"
>
+
</button>
// ...
Component A<div>Count: {state.count}</div>
You can see how it works here:
Or in GitHub: https://github.com/asmyshlyaev177/react-common-state-example
Check out my library for NextJS based on this principle https://github.com/asmyshlyaev177/state-in-url
Thanks for reading!