Global State in Gatsby, or Having and Eating Your Cake, Too
Tags: gatsby, react, webdev, wecoded
Published: 2021-05-04 00:00:32

Gatsby is a fantastic way to build and maintain static websites. React is a fantastic way to build and maintain dynamic web applications. But what if one wants to dream the ~~impossible~~ improbable dream of having your static cake while eating it dynamically, too? (Too many metaphors, I know.)

This was the problem I encountered when I needed to add and persist user data across an existing Gatsby site, without screwing up what was already there. Hopefully this will help the next developer who also dares to dream that improbable dream.

![Impossible dream gif](https://media.giphy.com/media/iD6Xk8BfSdqtwRVTWa/source.gif)

## The big Why?

First of all, _why_ was I setting out on this improbable task of adding global state to a static site? Was I so bored at work that my PM decided to let me run amok and make Gatsby harder? Not quite (though I do look forward to that day).

In reality, we were implementing a new feature that requires users to log in to be authenticated with Github, so that we could have access to visualize and run an audit on a private repository. That means we needed to persist that user state across several components, like the navbar that showed a different button depending on the user's logged-in state, as well as the components handling the audit logic. Thus far, we'd only used local state in the site, scoped to each component doing its thing. This was going to be a whole new world of Gatsby functionality for me.

![Aladdin gif](https://media.giphy.com/media/yyvSeRGVj4C64/source.gif)

## Finding imperfect solutions

The first task I set upon myself was research: was there a Gatsby plugin that could already do what I needed? (Spoiler alert: nope). How about existing tutorials? I already knew _what_ I needed: global state throughout my Gatsby app. I needed to learn the _how_, or at least be pointed to potential solutions. This short [blog post on global state in Gatsby](https://medium.com/swlh/gatsbys-global-state-management-with-react-s-context-5f8064e93351) gave me a great start, and led me to consider React's Context as a potential solution.

The next step for me is always: read the docs! Specifically, the [React Context docs](https://reactjs.org/docs/context.html). Providers! Consumers! Ok, this sounds like exactly what I need, except this was the React example...

```javascript
class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      ...
    };
  }

  render() {
	return (
       <ThemeContext.Provider value={this.state.theme}>
	       <Toolbar changeTheme={this.toggleTheme} />
	   </ThemeContext.Provider>
    );
  }
}

ReactDOM.render(<App />, document.root);
```

Huh. React apps have an `App` component. Gatsby apps do not. So I needed a Gatsby-specific way to do this Context thing. More research!

![Research gif](https://media.giphy.com/media/2wXs8n1LBQueHbzJIz/source.gif)

How about the Gatsby docs? That's where I found this incredibly helpful [tutorial on React Context with Gatsby](https://www.gatsbyjs.com/blog/2019-01-31-using-react-context-api-with-gatsby/), which got me started on the right path. Unfortunately, it's from 2019, which may as well be the prehistoric era when we're talking about React advancements. The example uses class components and no hooks (😱), and is all the way back in React 16.3, while we'd already been in the trenches with 16.8, and were up to 17. What are we, savages? It was definitely time for an update.

## Cake-baking time

With a decent understanding of React Context from their docs, as well as knowing I wanted to implement global state using [React Hooks](https://reactjs.org/docs/hooks-intro.html), specifically `useContext()` and `useState()`, I set about customizing and updating the sample code I had found to work for my use case.

### Building a user context and provider

In a nutshell, React Context is a way to set and use global state without passing a prop to every component that needs it. Instead, you create a Context, then set a Provider that provides that context throughout the application, and a Consumer that, well, consumes (or makes available) that context. Then, you use the `useContext()` hook to get the value of the global state object and, if needed, the function that sets the value in individual components.

In my `src` directory, I created `contexts/UserContext.js` and added the following:

```javascript
// ./src/contexts/UserContext.js
import React, { createContext, useEffect, useState } from 'react';
import { getCurrentUser } from '../utils/cognito';

const defaultState = {
  user: {
    loggedIn: false,
    userid: null,
    username: null
  },
  setUser: () => {}
};

const UserContext = createContext(defaultState);

const UserProvider = (props) => {
  const [user, setUser] = useState(defaultState.user);

  useEffect(async () => {
    const currentUser = await getCurrentUser();
    if (currentUser) {
      // Set the current user in the global context
      setUser(prevState => {
        return {
          ...prevState,
          loggedIn: true,
          userid: currentUser.id,
          username: currentUser.email
        };
      });
    }
  }, []);

  const { children } = props;

  return (
    <UserContext.Provider
      value={{
        user,
        setUser
      }}
    >
      {children}
    </UserContext.Provider>
  );
};

export default UserContext;

export { UserProvider };
```

Here, we're setting a `defaultState` - anyone who's used Redux should be familiar with that concept. It's the blank state every user visiting the website starts with.

Next, we're using React's [createContext](https://reactjs.org/docs/context.html#reactcreatecontext) API to create a context object based on our default values for `user` and `setUser`.

Then, we use the [useState](https://reactjs.org/docs/hooks-state.html) hook to set the `user` object, and initialize the `setUser` function that will be used to update that global `user` object.

The next hook we use is [useEffect](https://reactjs.org/docs/hooks-effect.html) - this was a new one for me, but essentially it's the Hooks-y way of triggering a `ComponentDidMount` / `ComponentDidUpdate` lifecycle event. When it's initialized with an empty array as in the example above, it acts as `ComponentDidMount`, in that it's only executed once on a render. That's perfect for our use case, as I want to call an async function called `getCurrentUser` (which is using the AWS Cognito API in the background to fetch user data), and if the user is already logged in, use the `setUser` hook to update the `user` object. If not, nothing happens and the `user` is still in the default state.

Finally, we use 
```javascript
    <UserContext.Provider
      value={{
        user,
        setUser
      }}
    >
      {children}
    </UserContext.Provider>
```

to wrap all of the children elements with the context of `user` and `setUser`. Then we export both `UserContext` and `UserProvider`, as we'll need both throughout our codebase.

### Wrap that root

So remember that example from the React docs that wrapped the root `App` component in the Provider? Yeah, that's not gonna work with Gatsby. Luckily, Gatsby has a super-handy [wrapRootElement](https://www.gatsbyjs.com/docs/reference/config-files/gatsby-browser/#wrapRootElement) API that basically does the same thing, and it's implemented in `gatsby-browser` like so:

```javascript
// ./gatsby-browser.js
import React from 'react';
import { UserProvider } from './src/contexts/UserContext';

export const wrapRootElement = ({ element }) => (
  <UserProvider>{element}</UserProvider>
);
```

That's all there is to it! Now each component will have access to the `UserProvider` context.

### What the Provider provides, the Consumer consumes

Next, we need a place for the `UserProvider` Consumer. This should be a parent element to the child components that will need access to the `user` context. In my codebase, I chose the `Layout` component, as it wraps just about every page of the site and is where we have another Provider already, the `ThemeProvider`. Your implementation may vary in this regard, but it's safe to say that most Gatsby starters do include a universal `Layout` component of some sort.

```javascript
// ./src/layouts/Layout.jsx
import React, { Fragment } from 'react';
import { ThemeProvider } from '@emotion/react';
import { Footer, NavBar } from 'layouts';
import UserContext from '../contexts/UserContext';

const Layout = (props) => {
  return (
    <ThemeProvider theme={theme}>
      ...
      />
      <UserContext.Consumer>
        {user => (
          <Fragment>
            <NavBar />
            {props.children}
            <Footer />
          </Fragment>
        )}
      </UserContext.Consumer>
    </ThemeProvider>
  );
};
```

As we can see, it's possible to use multiple Providers and Consumers within one app, though we'll keep our focus on the `UserContext.Consumer`.

Because we initialized 
```javascript
const UserContext = createContext(defaultState);
```
back in the context, we can access both `UserContext.Provider` and `UserContext.Consumer`. The code above just places the consumer above all the `children` components of the app. Because of the way the Consumer is set up, it __requires a function as a child__. That's why we have `<UserContext.Consumer> {user => (...)}</UserContext.Consumer>`.

Note that we're not passing the `user` object to the `NavBar` component here, though we very well could with `<NavBar user={props.user} setUser={props.setUser} />`. But then, how would we pass that same data to `{children}`? That's where the handy `useContext()` hook comes in!

### Hooks or it didn't happen

So we've got our Provider, we've got our Consumer in the site's `Layout` component, and now we need to pull the `user` object and, in some cases, the `setUser` function from the global context. Let's start with our `NavBar` component, which will either render a button or an icon depending on whether a user is logged in or not:

_Logged out view_
![nav-logged-out](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dn9vs2y41e51ajmrqc54.png)

_Logged in view_ 
![nav-logged-in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8kvnqwsppawwji2gcdjg.png) 


```javascript
// ./src/layouts/NavBar.jsx
import React, { useContext } from 'react';
import UserContext from '../contexts/UserContext';
import { signOut } from '../utils/cognito';

const NavBar = () => {
  const { user, setUser } = useContext(UserContext);
  const handleSignOut = () => {
    signOut();
    setUser({
      user: {
        loggedIn: false,
        username: '',
        userid: ''
      }
    });
  };

  return (
    <Header>
      <Nav className='navbar navbar-expand-lg fixed-top'>
        <a href='/' className='navbar-brand'>
          <img src={logo} />
        </a>
        <ul>
          ...other nav items
          <li className='nav-item nav-button'>
            {user && user.loggedIn
              ? <UserIcon handleSignOut={handleSignOut} username={user.username}/>
              : <a href='https://app.stackery.io/'><Button type='button' text='Sign In' /></a>
            }
          </li>
        </ul>
      </Nav>
    </Header>
  );
};
```

Let's start right at the top: we've got our `useContext` hook, and like a magical fairy pulling a bag of gold out of thin air, `useContext` pulls `user` and `setUser` out of the React ether and assures us that they're the global values our app depends on!

![Magic](https://media.giphy.com/media/l2YWs1NexTst9YmFG/giphy.gif)

So with `const { user, setUser } = useContext(UserContext);`, we can now see if the `user` object exists, and if `user.loggedIn` is true, we'll show a component called `UserIcon` that shows the user icon and has a dropdown that allows the user to sign out. And as we can see in `handleSignOut()`, we use `setUser` to update the global user state back to the defaults when the user has signed out.

### Context in class components

Finally, we've got a class component where we also need access to the `user` object and `setUser` function. There are two options for this: if you have a direct parent component that's a functional component, you can pass those values as props like so:

```javascript
// ./src/pages/Registry.jsx
import React, { useContext } from 'react';
import { RegistryContainer } from 'components/registry';

const Registry = () => {
  const { user, setUser } = useContext(UserContext);

  return (
    <Layout>
		...
       <RegistryContainer user={user} setUser={setUser} />
    </Layout>
  );
};

export default Registry;
```

Then in `RegistryContainer`, we access `user` and `setUser` just as we would any other props in a class component:

```javascript
// ./src/components/registry/RegistryContainer.jsx
import React, { Component } from 'react';

class RegistryContainer extends Component {
  constructor (props) {
    super(props);
    ...
    }

  async componentDidUpdate (prevProps, prevState) {
    const {
      user
    } = this.props;

    if (user.loggedIn && !user.githubAuthState) {
      // do the oauth things!
      ...
      // then update global user
        this.props.setUser({
          ...this.props.user,
          githubAuthState: githubAuthStates.AUTHED
        });
    }
  }
  ...
  render () {
    return (
      <RegistryForm
        ...
        user={this.props.user}
      />
    );
```

Option two, which I did not end up implementing, uses the [contextType](https://reactjs.org/docs/context.html#classcontexttype) class property, and would look something like this:

```javascript
// example from https://www.taniarascia.com/using-context-api-in-react/
import React, { Component } from 'react';
import UserContext from '../contexts/UserContext';

class HomePage extends Component {
  static contextType = UserContext;

  componentDidMount() {
    const user = this.context

    console.log(user) // { name: 'Tania', loggedIn: true }
  }

  render() {
    return <div>{user.name}</div>
  }
}
```

Either way should work depending on your codebase, I just went for the clean and simple `useContext()` option throughout.

## The cake is not a lie!

![Cake cat gif](https://media1.tenor.com/images/2dadddeaa75eb0517e560938a9194c0b/tenor.gif)

And there we go: we've got access to our `user` anywhere we fancy on our Gatsby site, and all it took was a little bit of Context.

So now, if a user is logged in and on the Registry page, they'll see the same state in two different components:

![logged-in-state](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0a0ja6ij7lk63iij4fyt.png)
 
And if they're logged out, all the components know:
![logged-out-state](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v30f0zifvl4squs5u3br.png)

Hope this is helpful to future Gatsby tinkerers, and feel free to ask questions or point out bugs (no, using semi-standard is not a bug) in the comments. And if you'd like to see this functionality live in the wild, check out [stack.new](https://www.stackery.io/registry/) for yourself!