📥 Sharing state in Gatsby
- Published on
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.