Local State management with React hooks and the Context API using Typescript

Sean Rennie
4 min readSep 27, 2019

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.

  1. Lukes article mentioned above
  2. React hooks (useContext & useReducer) and the context API
  3. 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 to useState. 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.

--

--

Sean Rennie

Software engineer with a love of maps and anything geospatial. I enjoy techno, the outdoors, tequila & coffee. Currently working at Sensat Ltd