An Idiosyncratic Blog

📥 Sharing state in Gatsby

Published on
6 minutes read

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.

We'll start with a really simple Context Provider, that exposes a hook which can be used in components in the entire application.

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 be 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,

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
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 entry point App component with SimpleContext and call it day. But this is not Create React App, so we have to 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

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.

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.