Handle global state in a Gatsby app without using Redux
Last week I post a blog about fetch data from the database/API with useEffect
and useContext
. That is going to enable the component to fetch some data every time it is rendered. But what about those global states that would be shared by different components scattered all over the application?
What I usually do in a React app
For example, when we have a user component and we want to pass it to Dashboard
and NavBar
, what can we do? Since Gatsby is a React framework, I try to compare how I implement it in React. I remember I will use Redux
. Just in case you don’t know too much about Redux
, I will explain some more.
Since usually React is used to produce a single page application, there is an index.js in React which represents that single page, in other words, the whole app. The architecture usually looks like:
index.js
-AppComponent.js
-componentA.js
-componentB.js
...
Therefore, Redux will wrap App
component with something called store
, which includes all states or dispatch methods and thus all components within the App
component would have access to those states or methods .
What I can do in the Gatsby app
Modify the htmj.js
In the Gatsby app, there is no such thing called index.js
by default. There is another thing called html.js
which is usually hidden. You can usually unveil it with this command:
cp .cache/default-html.js src/html.js
And now you will see it and insert HTML into header
and footer
or add some custom JavaScript(where you can grab the data).
But usually, I don’t use that because I think there’s a reason that it is hidden, and don’t want to by accident mess up the configuration. (Let me know if you know why it is hidden though 😆). Also, to insert some lengthy JS codes in a file that looks like HTML(even the suffix is js
) is really ugly and doesn't make sense to me.
Set up a Layout
Component
The second way is to manually set up a Layout
component and grab all the data that I need there using useEffect
and useContext
just like what I illustrated in the last blog. Then a Layout
component will probably look like this:
import React,{useState, createContext } from "react"import Navbar from "./navBar"export const SomeContext = React.createContext(null);// don't forget to export, so that it can be used outside of this componentconst Layout = ({ children }) => {
const [user, setUser] = useState(null) useEffect(() => {
getUser = async() => {
let user = await fetchData()
setUser(user)
}
getUser();
, []
})return (
<SomeContext.Provider value={{user}}>
<Navbar />
<main>{children}</main>
</SomeContext.Provider>
)
}export default Layout
In this way, everything we wrap with Layout
component will automatically become the children
and would be able to use the user
object that is provided through SomeContext
.
Therefore, we can wrap up all of the components within this Layout
component, just like so:
const AnyComponent = props => { return <Layout>{any component content}</Layout>
}
Done! Now every time you want to use the user
, you can use it like so:
import React, {useContext} from "react"
import {SomeContext} from "../layout"const AnyComponent = props => {
const {user} = useContext(SomeContext) return <Layout>{user.name}</Layout>
}
Handle Layout
component if you are a lazy person
That’s said, even we can use the user
object anytime we want with the context
API, it is kind of annoying to have to wrap up all components with Layout
component manually every time and import it on every file.
What if we can import Layout
component somewhere and the application would automatically wrap up with it? That sounds awesome!
The trick is: use wrapPageElement
.
Go to the root folder and put this on gatsby-ssr.js
and gatsby-browser.js
:
import Layout from './src/components/layout'export const wrapPageElement = ({element, props}) => {
return <Layout {...props}>{element}</Layout>
}
Now you don’t have to import Layout
component all the times and it will be rendered automatically with other components! Bravo!
Note
Besides wrapPageElement
, there is one called wrapRootElement
. Their difference is illustrated in this article. For those who don’t want to read, here’s the straight answer I picked from there:
…yes, you can use wrapPageElement element for everything, but it’s better to use it only for providers that need the router props, or for page transition layouts, and use wrapRootElement for any other provider, like theme and global state providers.
Thanks for reading!