Typesafe Global State with TypeScript, React & React Context

Image for post
Image for post

Whilst working at TfL, I was one of the lead web developers involved in rewriting the their online forms (i.e. Lost Property, Ideas, Font Requests etc.) from an ASP.NET site to a decoupled TypeScript React & .NET Core solution. The Accessibility Guides web form was the pilot form for this rewrite, outlining a patterned structure for rewriting other TfL online forms.

Image for post
Image for post
Giving a facelift to the accessibility guides form — TfL ©

An important technical design decision that needed to be made early on was which, of the multiple state management systems, would we use to store state for this web form. Redux? MobX? Flux? React Context? Truth be told, RecoilJS had just launched and I was pretty hyped to try it out on a production application — alas, it probably would not have been pertinent to use an experimental state utility on a site that would serve 10,000+ users/month!

In this case, perhaps the de facto choice for state management would have been Redux due to its ubiquity in the React world; however even with Redux Toolkit, the learning curve isn’t easy for users from a non-JS / TS background.

As a predominantly OOP C# shop, MobX could have been another clear choice because of similarities with class based structures. Although less of a learning curve, still not as well documented as Redux or Context.

The library I ended up pushing for amongst my colleagues was React Context for the following reasons:

  • It comes baked into React itself — no extra , no extra bytes in the final compiled version being served to users.
  • It’s well documented by React — most of my personal questions
  • Easy to create a simple hook abstraction for inexperienced React developers — this was probably the most important reason in my mind. We could create a pattern for getting and setting state that was not complicated, and required little to no learning curve to use.

I presented the state management options and we went with React Context using the approach that I’m going to outline below:

So if you’re already bored with my intro — the tldr; version can be found here. The code below is built on top of Create React App for brevity & simplicity.

For the purposes of this overview, we’re going to put all the code relating to our global state in a single file called. You probably could abstract the code into multiple files / folders that better fit your structure.

When creating a typed global state with React Context the first step is creating the interface to that state. Inside our :

Next, we need to create the context provider:

This creates a default context object with a property of that takes the shape of a of our that we declared above. It also takes a property of which is an object (a.k.a. a function) that is of type . MAN THAT WAS A MOUTHFUL!

The observant of you may have noticed that these types mirror those of the hook array. Well noticed! This is because at the core of our we are using . You’ll see below.

Next we need to set up the context Provider. This is the component that we use as a parent component around any child components that need to access the global state.

In this snippet we are effectively creating a wrapper that can be used at the top level of our app. It makes anything we put into the prop object available to any . You can see here we are passing the value and function from React’s hook

Of particular note in this snippet is that we are providing the parameter a default value of an empty object that takes the shape of our . This allows us to pass a default initial state at the top level where we consume our GlobalStateProvider. This is very useful for using the wrapper when testing!

Ok so we’ve done all the boilerplate-y setup, we’re now onto consuming & setting our state 🎉.

In this snippet we are creating a hook that unpacks the using React’s hook and returns an object containing our and the function (as we did above)

Ok so you’ve made it this far…congrats! Here’s what your file should look like:

Now, in your application you need to wrap all the pages / components that need to use your state, I did this in the beneath the React Router component:

And now in your child page page you set your state (see )

And to read your state:

And that’s it! Probably my favourite thing about this method of state management is:

  • It’s simplicity — it’s almost exactly like using the hook, and therefore makes it very accessible to developers not yet familiar with global state management tools, but familiar with setting local state.
  • It’s type-safety — the compiler will throw an error if you try and put any content in your object that you didn’t define in your interface 😄🚀

If you check out the repo, you can also see the BONUS CONTENT of a component that writes your state object into a viewable format. It’s something of a poor man’s redux-dev tools 🙈.

Image for post
Image for post
That’s All Folks! © Warner Bros., via Wikimedia Commons

I hope you enjoyed this article, and hopefully it helped you in creating ️.

What I haven’t covered in this article is the performance issues of using this method for large scale global state. It’s quite well known that using React Context for high frequency updates & in multiple components can cause issues, primarily because anything that consumes the React Context state gets re-rendered. This state management is meant for small typed JSON objects to be passed between multiple pages in low-frequency updates, (ideal for a form).

If you have any questions or comments feel free to leave a comment below, or ping me on Twitter @JamieADHaywood.

Written by

Entrepreneur & Lead Developer @ Captur.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store