- Bundled with Parcel 2.0.1
- Minimal all-in-one state management with async/await support
The following steps are already done, but describe how to use
src/utils/stateto create and use your ownstoreandStateProvider.
- Create a file e.g.
/state/app.jsand add the following code
import { State } from '../utils/state';
// example
const initialState = {
app: {
mounted: false
}
};
export const { store, Provider } = State(initialState);- Now in your
index.jswrap yourAppcomponent with theStateProvider
import { Provider } from './state/app';
ReactDOM.render(
<Provider>
<App />
</Provider>,
document.getElementById('root')
);- Finally in
App.jsyou canuseContext(store)
const { state, dispatch, update } = useContext(store);<p>Hello {state.foo && state.foo.bar.hello}</p>const handleClick = () => {
update('clicked', !state.clicked);
};const onMount = () => {
dispatch(onAppMount('world'));
};
useEffect(onMount, []);When a function is called using dispatch, it expects arguments passed in to the outer function and the inner function returned to be async with the following json args: { update, getState, dispatch }
Example of a call:
dispatch(onAppMount('world'));All dispatched methods and update calls are async and can be awaited. It also doesn't matter what file/module the functions are in, since the json args provide all the context needed for updates to state.
For example:
import { helloWorld } from './hello';
export const onAppMount = (message) => async ({ update, getState, dispatch }) => {
update('app', { mounted: true });
update('clicked', false);
update('data', { mounted: true });
await update('', { data: { mounted: false } });
console.log('getState', getState());
update('foo.bar', { hello: true });
update('foo.bar', { hello: false, goodbye: true });
update('foo', { bar: { hello: true, goodbye: false } });
update('foo.bar.goodbye', true);
await new Promise((resolve) => setTimeout(() => {
console.log('getState', getState());
resolve();
}, 2000));
dispatch(helloWorld(message));
};The default names the State factory method returns are store and Provider. However, if you want multiple stores and provider contexts you can pass an additional prefix argument to disambiguate.
export const { appStore, AppProvider } = State(initialState, 'app');The updating of a single store, even several levels down, is quite quick. If you're worried about components re-rendering, use memo:
import React, { memo } from 'react';
const HelloMessage = memo(({ message }) => {
console.log('rendered message');
return <p>Hello { message }</p>;
});
export default HelloMessage;Higher up the component hierarchy you might have:
const App = () => {
const { state, dispatch, update } = useContext(appStore);
...
const handleClick = () => {
update('clicked', !state.clicked);
};
return (
<div className="root">
<HelloMessage message={state.foo && state.foo.bar.hello} />
<p>clicked: {JSON.stringify(state.clicked)}</p>
<button onClick={handleClick}>Click Me</button>
</div>
);
};When the button is clicked, the component HelloMessage will not re-render, it's value has been memoized (cached). Using this method you can easily prevent performance intensive state updates in further down components until they are neccessary.
Reference: