Nestled deep within Gatsby's docs, there's a couple of hella useful API functions: wrapPageElement
and its big brother wrapRootElement
.
These APIs give the opportunity to develop richer, complex UIs on top of Gatsby's static page generation.
By exporting these functions we can let Gatsby know how it should wrap additional components around our pages, both at the app's root level and nested one deeper at the page level.
This comes in use when persisting components across routes, essential to some of the below features:
- Media Player / Controls
- Navigation elements
- Sidebars & Menus
- App-level context providers (like
Redux
)
In a rush? Check out the quick implementation.
What do you mean by persist?
Well, Gatsby's default behavior is to re-render all elements on a page whenever we change route.
As described above we may need components to stay alive and mounted across routes or persist.
In this article we're going to focus on using wrapPageElement
to keep a layout component persistent across routes. There are multiple ways to do this, but I'm going to show you what I've found to be most elegant.
Assigning Layout Components
What I'm calling a layout component can be any component that wraps around your page. A really basic example might include a header and a footer that sandwich your page's content.
// components/Layout.js
export const Layout = ({ children }) => (
<>
<h1>My awesome site!</h1>
{children}
<footer>Built with Gatsby.</footer>
</>
)
The sky's the limit with these layouts, anything that we want to keep alive across pages can be stuck into one. The only requirement is that it renders {children}
so the wrapped page is visible.
We need to create a way of linking a layout component to a specific page component so that we can tell Gatsby how to correctly wrap it.
This is pretty easy, we'll just assign a static Layout
property to our page component.
// pages/index.js
import Layout from "../components/layout"
const IndexPage = () => {
return ...
}
IndexPage.Layout = Layout
export default IndexPage
Notice Layout
is uppercase just as a convention to indicate that we're working with a React component.
That's all that's required for the page, now onto Gatsby config.
Setting up our Gatsby files
Now we need to tell Gatsby that we want it to wrap the page we've chosen with the assigned Layout
component.
Within gatsby-browser.js
and gatsby-ssr.js
we can export wrapPageElement
. When Gatsby calls this function for each page it passes it two arguments; an element and some props.
// gatsby-browser.js && gatsby-ssr.js
export function wrapPageElement({ element, props }) {
const Layout = element.type.Layout ?? React.Fragment
return <Layout {...props}>{element}</Layout>
}
The element is simply the page component Gatsby wants to render, while the props is that page's props. These props include all kinds of useful data including any GraphQL queries made. (Check out more here)
We assign the Layout
variable using the nullish coalescing operator (??) which checks if there's a Layout property on our page component, if not it just returns a React fragment
It's important that we spread the page's props onto the Layout
so that any queries within the layout can be accessed via its own props.
In almost all cases you'll want to keep your gatsby-ssr
& gatsby-browser
config files the same so that your components can correctly be hydrated.
The result
That's it! If we assign the same Layout
to multiple pages and navigate between them Gatsby will make sure they're not re-rendered. This keeps any state within those layouts intact across routes.
I've put together a repo containing a minimal example showing how a counter component keeps counting when routes change. Check it out if you want to dig deeper.
https://github.com/miles-crighton/gatsby-persistent-ui
What's great about this method is it scales to how ever many layouts you want while being completely opt-in.
If you don't want a layout on a page, don't set a Layout
property - simple!
Top comments (0)