- 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/state
to create and use your ownstore
andStateProvider
.
- Create a file e.g.
/state/app.js
and 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.js
wrap yourApp
component with theStateProvider
import { Provider } from './state/app';
ReactDOM.render(
<Provider>
<App />
</Provider>,
document.getElementById('root')
);
- Finally in
App.js
you 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: