DEV Community

loading...
Cover image for Keeping persistent UI across routes with Gatsby's wrapPageElement

Keeping persistent UI across routes with Gatsby's wrapPageElement

milescrighton profile image Miles Crighton ・3 min read

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.

wrapPageAPIs

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>
  </>
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
}
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

If you don't want a layout on a page, don't set a Layout property - simple!

Discussion (1)

pic
Editor guide