📥 Sharing state in GatsbyMay 31, 2021Gatsby 414 words

In the last post, I showed how we can create a global layout for an entire app and have it shared between the pages without re-renders.

In this post, we’ll go one step further and create a shared state management provider, that wraps the whole Gatsby site. There are loads of state management solutions like Redux, Zustland, etc. but for our use case, I’ll simply use the Context API which React provides out-of-box.

This post assumes that you, the reader, are familiar with React Hooks, Context APIs, and the Gatsby folder structure.

I’ll create a really simple Context Provider, which exposes a hook so that it can be used in components.

// src/context/SimpleContext.jsx

import { createContext, useContext, useState } from "react";

const defaultState = {
  data: {},
};
const SimpleContext = createContext(defaultState);

export const SimpleContextProvider = ({ children }) => {  const [data, setData] = useState(defaultState);
  const updateState = (_data) => setData(_data);
  const contextValues = {
    data,
    updateState,
  };
                                                        
  return (
    <SimpleContext.Provider value={contextValues}>
      {children}
    </SimpleContext.Provider>
  );
};

export const useSimpleContext = () => {  const context = useContext(SimpleContext);
  if (context === undefined || context === null) {
    throw new Error(
      `useSimpleContext must be called within SimpleContextProvider`
    );
  }
  return context;
};

SimpleContext has a data state, which can by updated by calling the updateState method. I also expose a useSimpleContext hook, to keep the component code clean.

Now for the pages which use SimpleContext,

// src/pages/BarApp.jsx

import React from "react";
import styles from "./bar-app.module.scss";
import { useSimpleContext } from "./context/SimpleContext";

const BarApp = ({ brand }) => {
  const { data } = useSimpleContext();  return (
    <div className={styles.container}>
      <span className={styles.text}>{data}</span>
    </div>
  );
};

export default BarApp;
// src/pages/AppBar.jsx

import React from "react";
import styles from "./app-bar.module.scss";
import { useSimpleContext } from "./context/SimpleContext";

const AppBar = ({ brand }) => {
  const { updateState } = useSimpleContext();  
  useEffect(async () => {
    const res = await fetch(...); // Get some data from an API
    const json = await res.json();
    updateData(json);  }, [])
  
  return (
    <div className={styles.appBar}>
      <span className={styles.text}>App Bar</span>
    </div>
  );
};

export default AppBar;

I’ve made two pages here, AppBar and BarApp. (Yeah I know, real creative with the names). AppBar will fetch some data from an API and update the state, and BarApp reads the data from SimpleContext and renders it in the UI.

This is typical React, if we had to share SimpleContext in a normal Create React App, we’d wrap the entrypoint App component with SimpleContext and call it day. But this is not Create React App, so we gotta do a little work.

The wrapRootElement API is a perfect fit for this use case. This API allows you to wrap your root element with a wrapping component, e.g. a Provider from Redux or… a SimpleProvider from React Context.

You’d probably ask, why can’t we wrap the SimpleProvider in a layout component, since that is being shared by all the pages? The problem with that is the SimpleProvider would be executed for every page. wrapRootElement lets you set the root React Element in the entire tree and makes sense to set Providers like Context APIs, Redux etc.

Now all that’s left for us to do is to wrap out entire app with SimpleContextProvider

// ./gatsby-browser.js

import React from "react"
import { SimpleContextProvider } from "./src/context/SimpleContext"

export const wrapRootElement = ({ element }) => (
  <SimpleContextProvider>{element}</SimpleContextProvider>
)

And that’s it. Now the entire app can use the shared data from SimpleContext.

Note: There is an equivalent hook in Gatsby’s SSR API. It is recommended to use both APIs together, otherwise the build might fail. So create a gatsby-ssr.js file and copy the contents from gatsby-browser.js.

What are the use-cases you ask? You can use this pattern to create a ThemeProvider to switch the theme of your site, AuthGuard to manage access to authenticated pages, i8nProvider to support multiple languages on your site etc.

What’s your use-case for building an app-wide state management solution? Let me know in the comments below.