Because asynchronous immutable React state is a tedious overkill for small applications
import React from 'react';
export default class Counter extends React.Component {
data = {count:1}
up = ()=>this.forceUpdate();
render() {
const {data, up} = this;
return (
<button onClick={()=>up(data.count++)}>
{data.count}
</button>
)
}
}
import React from 'react';
// -- useConstructor helper
// -- contrary to useEffect, this runs BEFORE the first render
function useConstructor(fn) {
const self = React.useRef({}).current;
self.forceUpdate = React.useReducer(x=>!x, false)[1];
if (!self.init) {
self.init = true;
fn(self)
}
return self;
}
// -- Sync state
export default function Counter () {
const {data, up} = useConstructor((self)=>{
self.data = {count:1}
self.up = ()=>self.forceUpdate();
});
return (
<button onClick={()=>up(data.count++)}>
{data.count}
</button>
)
}
Example implementation for React, for learning purposes, but not recommended as when sharing state you usually want to share actions
as well to centralize all types of state changes in one place.
Due to this, better use this instead, that is also compatible with Vanilla or Svelte: https://gist.github.com/drodsou/3856fca1e9da003d7c9c825ee3497be7
import React from 'react';
// -- the store
function createMutableSharedStore(data = {}) {
const _subs = new Set()
const store = {
data,
up: ()=>{
for (let sub of _subs) {
console.log('sub exec');
sub() // forceupdates
}
},
subscribe : (fn)=>{
_subs.add(fn);
return ()=>_subs.delete(fn) // unsubscribe
}
}
return store;
}
// -- hook helper
function useMutableSharedStore (store) {
const forceUpdate = React.useReducer(x=>!x, false)[1];
React.useEffect(()=>{
return store.subscribe(()=>forceUpdate()) // return unsubscribe
},[]);
return store;
}
// - use
const store = createMutableSharedStore({count:2});
// -- example use on hooks
function Counter1 () {
// -- use store (includes sub/unsub)
const {data,up} = useMutableSharedStore(store);
return (
<button onClick={()=>up(data.count++)}>
{data.count}
</button>
)
}
function Counter2 () {
// -- use store (includes sub/unsub)
const {data,up} = useMutableSharedStore(store);
return (
<button onClick={()=>up(data.count++)}>
{data.count}
</button>
)
}
// -- example use on class
class Counter3 extends React.Component {
componentDidMount() {
this.storeUnsubscribe = store.subscribe(()=>this.forceUpdate())
};
componentWillUnmount() {
this.storeUnsubscribe();
}
render() {
const {data,up} = store;
return (
<button onClick={()=>up(data.count++)}>
{data.count}
</button>
)
}
}
export default function Page () {
return <>
<Counter1/><Counter2/><Counter3/>
</>
}