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.
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
yarn install, 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:
For the purposes of this overview, we’re going to put all the code relating to our global state in a single file called
GlobalStateProvider.tsx. 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
state that takes the shape of a
Partial of our
GlobalStateInterface that we declared above. It also takes a property of
setState which is an object (a.k.a. a function) that is of type
Dispatch<SetStateAction<Partial<GlobalStateInterface>>>. MAN THAT WAS A MOUTHFUL!
The observant of you may have noticed that these types mirror those of the
useState() hook array. Well noticed! This is because at the core of our
simple-global-state™️ we are using
useState(). 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
value prop object available to any
children. You can see here we are passing the
state value and
setState function from React’s
Of particular note in this snippet is that we are providing the
value parameter a default value of an empty object that takes the shape of our
GlobalStateInterface. 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
GlobalStateContext using React’s
useContext hook and returns an object containing our
state and the
setState function (as we did above)
Ok so you’ve made it this far…congrats! Here’s what your
GlobalStateProvider.tsx 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
App.tsx beneath the React Router
And now in your child page page you set your state (see
line 10 & 13)
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
useStatehook, 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
<Debug> component that writes your state object into a viewable format. It’s something of a poor man’s redux-dev tools 🙈.
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.