Since useAuth is taking off, I wanted to talk about the global state management technique that makes it work. You might find it useful too, or think it’s obvious, I was pretty excited when it clicked 🙂

And when I say useAuth is taking off …

Click through for source

Yep 🔥

Ok so global state management, how do you do it? You grab Redux or MobX, or maybe Unstated or Constate and you’re done. Right?

All those libraries give you a global store to put your data, allow every component to access that state, and manage re-renders. You get some way of updating that state and life is good.

Sometimes there’s extra features.

But what if you don’t want to learn yet another library? You just wanna share some state between a bunch of components. What then?

Global state without the hassle

The simplest approach is to put global state in your top-level component. Pass it down through props and if you want to change it, pass down some callbacks too.

Works great and gets out of hand real fast. Passing props is cumbersome and you can create a situation where every little update re-renders your entire app.

Here’s a better solution 👉 You can share the state and dispatch from useReducer through context and access them with a hook.

mind blown

I’ll explain using useAuth as an example, but the same approach works for anything.

There’s 3 parts to this story:

  1. A context provider component
  2. A reducer for state
  3. A hook providing our API

Users render the context provider somewhere close to the root of their app. Similar to a theme provider, Apollo provider, etc.

The provider feeds state from our reducer into the context. When you fire actions on the reducer, state in the context changes, and every component using the useAuth hook re-renders with new data.

It’s pretty great.

AuthProvider

file link

Click through for source

That’s our context provider.

The provider initializes auth0 using Auth0’s API and configures some properties. Full version supports a bunch of config via props, overwriting defaults, etc.

We call useReducer to get a state object and the action dispatch function. Initialize state from local storage if you want persistence, or keep it empty to start with.

I recommend defining initial values so your code doesn’t have to deal with undefineds.

Our provider renders AuthContext.Provider and as its value uses the state object, dispatch function, and auth0 object.

This was the trick to getting different useAuth hooks to talk to each other. Using reducer state and dispatch as the context value.

Using AuthProvider

In Gatsby you use the auth provider in gatsby-ssr and gatsby-browser to wrap your entire component tree. With create-react-app this goes around your App component, with other frameworks who knows.

The higher up it renders, the more parts of your app share state.

Click through for source

Auth reducer

The auth reducer takes care of state transitions. full file

Click through for source

If you’re not familiar with reducers: it’s a function that takes the current state and an action, and calculates the next state. Always returning a fresh object.

In our case the login and logout actions handle the user model and cookie expiration time. Both actions also save data to local storage so it persists across page reloads.

useAuth hook itself

With heavy lifting moved to the context provider and auth reducer, our useAuth hook becomes a sort of thin wrapper and API provider. full file

Click through for source

The state management part of useAuth is that useContext call. It hooks into the context shared by AuthProvider and gets access to the state object and dispatch method.

That gives any component that uses useAuth access to the same shared state.

You could use useContext directly. I prefer using a custom hook because it makes your code easier to read.

What’s better, doing this everywhere you want to access some shared state:

Click through for source

Or this

Click through for source

I know what I prefer 🙂

With a custom hook you can use dispatch with carefully controlled values internally. Nobody needs to know what’s going on. All they need to remember is your convenient API.

✌️

So that’s how you can use React hooks to build powerful abstractions on top of global state shared by all.

What do you think?

Cheers,
~Swizec

Learned something new? Want to improve your skills?

Join over 10,000 engineers just like you already improving their skills!

Here's how it works 👇

Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

PS: You should also follow me on twitter 👉 here.
It's where I go to shoot the shit about programming.