Local State management with React hooks and the Context API using Typescript
An adaption of Luke Hall’s great article, State Management with React Hooks and Context API in 10 lines of code, for my React Typescript app. The obvious result being type-safety for your global state and IntelliSense when using VS Code
Credit where credits due: It must all go to Luke for his article. It really is an elegant solution.
You should be comfortable with these things
This article assumes you are familiar with a few things as I wont go into the background of each.
- Lukes article mentioned above
- React hooks (useContext & useReducer) and the context API
- Typescript in a React project.
Disclaimer: I don’t claim to be a Typescript guru so if you spot any some errors please be nice and point them out in the comments.
Some Background
The project I am working on uses Apollo Client for data fetching and as of v2.6
I have started using the newly baked in local state management as well. I’ve enjoyed the transition and it works well in most cases, however the project uses google maps and has the need for passing around references to features drawn on the map. Using Apollo requires stringent typing with the Scheme Definition Language (SDL), something I didn’t want to have to do for a map feature reference (not sure its even possible). Cheekily I can use an any
type with Typescript for my feature reference, which you cant do with SDL. I know this precludes the type-safe claim but if I where using any other state I’d be sure to add typings.
The goal
Create a self contained provider component with a predefined reducer and initial state. Access and update the providers context from anywhere using a custom hook.
To achieve this I need to create three things: MapContext
and MapProvider
components and a useMapState
hook. I defined all three of them in a file called MapState.tsx
at the root of my map module. Colocation for the win!
1: MapContext
I create the MapContext
component with createContext
. By passing it a well typed initial value it will keep things type-safe and ensure we get the IntelliSense we want. Obviously here I am using any
for the featureRef
like I explained above, while this doesn’t give me any type-safety I will still get the IntelliSense. If any one knows where get typings for a google map polygon reference that has all the methods I would greatly appreciate it.
The MapState
is simply the state we want to hold, in our case it is featureRef
. I need two MapActions
, one for setting the next featureRef
and one for resetting it. Using a Typescript union’s ensures that when I updated the context I don’t accidentally pass a feature reference when I’m trying to reset it. MapActions
are used together with the React.Dispatch
because it will ultimately be returned as a Dispatch
type from useReducer
. See below.
2: MapProvider
To create the MapProvider
we need the MapContext
, a reducer
function, some initial state and React’s useReducer
hook.
From the docs:
useReducer
is an alternative touseState
. Accepts a reducer type(state, action) => newState
, and returns the current state paired with a dispatch method.
By using useReducer
in our provider we can hold the state we need in the context as well as getting the useReducer
dispatch method for update that state.
I use the reducer
and the initialState
with useReducer
within the MapProvider component to get the state
and a dispatch
method. I’ve renamed them to something more useful, mapState
and setMapState
and pass them both to <MapContext.Provider>
's value
prop as an object.
By defining all of this together I don’t need to pass MapProvider
anything when its rendered. It will already hold my initial state and a dispatch on the context which I can access down the tree.
3:useMapState
hook to access and update the state
Because MapProvider
sets up a context with everything I need when it’s rendered, all I need to do is access that context lower down in the tree to use and update the state. React’s useContext
hook makes this really easy by exporting a new custom hook useMapState
as an arrow function that returns the MapContext
.
Putting it all to work
Simply import and wrap the map module components with MapProvider
like so:
Then from within any map module component, import the useMapState
hook and enjoy the intellisense and local state management made simple.
Conclusion
I love the simplicity of using this system once the initial set up is done. Leveraging the power of hooks and the context API along with Typescript makes for an awesome developer experience in my humble opinion.
If anyone is interested I will put together a code sandbox of this example.
Thanks for reading. This is my first blog and would really appreciate any feedback in the comments below. A big thanks once again to Luke Hall for his elegant solution and for inspiring this article.