In an application that doesn’t need to store and manage a complex state, it is sometimes better to skip adding the weight and complexity of a full blown state management library. For example, to store things like: auth tokens, user basic info, user settings, etc, a simple store using React context and state is enough. React 16.8 hooks can simplify this even more.
Note: I am not saying React context and state will completely replace the usage of Redux, MobX or other state management libraries. There is no cookie cutter solution, what library you use depends on the application needs, complexity, context and the dev team’s personal taste on what they favor more or are more motivated by.
React’s setState hook can be used to hold the store state and createContext (together with the context Provider and the useContext hook) can be used to pass the store state down to the components that need it. A simple form of the store would be something like:
The store is just:
- A StoreProvider component that holds the state and should be placed near the root of the React component hierarchy.
- A useStore hook that returns an array with the state and the setState function.
The context value (returned by the useStore hook) is an array to allow the caller to easily pick the name of the state and setState variables (React hooks use this pattern a lot). The useStore hook can’t return the context value array instance directly because that would allow the caller to change the array, so a new array is returned.
Notice the usage of useMemo in the StoreProvider to ensure that the context value array instance only changes when the state value changes. This will prevent unnecessary re-renders of pure components.
The usage of the useState hook has the drawback of the losing the previous state shallow merge on setState calls like the class component setState does. This means calls to setState will override the previous state instead of merging it with the new state. To add this behaviour we need to modify StoreProvider a bit:
The resolveNextState function resolves nextState to a value, it is used to handle the possibility of nextState being a function that receives the previous state and returns the new state. The mergeState function takes care of merging the previous state with the next state if they are objects.
useCallback is used to memorize our setState merge enhancement. Notice the usage of  as dependencies for useCallback (the second parameter), since React guaranties setState of useState will not change, there is no need for setState to be a dependency of useCallback.
An example of using the store could be something like this:
There is clearly a pattern that stands out, the state needs to be checked for falsy values and the user property needs to be extracted from the state. We could use object destructuring and property initialization, but that just changes the syntax, we still repeat it every time we want a property from the state.
It is easy to include a useStoreProp hook in our store that solves this recurring pattern by receiving the name of the state property (“user” for example) and returning an array with the state property value and a propSetter function that will set the property value in the state:
The useStoreProp hook not only avoids having to repeat code to extract the state property values, but also end’s up isolating and decoupling the root state properties a bit. It encourages you to isolate the property state inside the feature that uses it, making the store’s state more modular. If you use useStoreProp hook several times in he same component, it is likely you should either refactor that component into several components or redesign your state schema.
If you try to use this hook to store user settings or auth tokens you will quickly find yourself asking:
“how do i set the initial state that i kept stored in localStorage or somewhere else?”
You probably want to set the initial value on the state property level and not set all state properties values together in a global state initializer. If useStoreProp allowed to set the initial property state, the state properties would be modular and completely independent of each other:
This version of useStoreProp checks if the state has a property with the name propName, if it doesn’t it will resolve the initValue and return it has if it was the state’s property value. useEffect is used to set the state property value in case the initValue was used. This will ensure the state is set with the initValue value but has the drawback of producing a re-render with the same state values. This re-render can be mitigated by using pure components.
If you live in HOC (Higher Order Component) world, it is really easy to make HOCs from the useStore and useStoreProp hooks:
Notice the usage of memo function to prevent re-renders when the same exact props are used.
Using only React state, context and some hooks we managed to implement a simple state store that lets you treat its root properties in a modular and decoupled way.
*actually full stack but i really wanted to make that Spider-man joke :p