当サイトは、アフィリエイト広告を利用しています

【Gatsby】pageコンポーネント間でstateを共有する方法

作成日:2022月10月28日
更新日:2022年10月28日

Gatsbyでは通常はページ遷移をした場合、pageコンポーネントがレンダリングされるため
stateを共有できなかった。 Gatsbyでpages間を跨いでstateを共有する
方法を調べてみたのでまとめておく。
具体的にはGatsbyBrowserAPIsの下記のいずれかを使う必要がある

  • wrapRootElement
  • wrapPageElement

上記に加えて、hooksのuseContextも利用する。
wrapRootElementとwrapPageElementのどちらでもpage間の状態維持は
可能だがwrapRootElementの方が適していると思う。

GatsbyBrowserAPIsとは?

rootへの設定、グローバルに読み込ませたい設定などをすることができる
GatsbyのAPIのこと。
※wrapRootElementとwrapPageElementもGatsbyBrowserAPI
詳細は下記参照

gatsby-browser.jsとは?

GatsbyBrowserAPIを使用するためのファイル
GatsbyBrowserAPIを使用するにはプロジェクトルートに
gatsby-browser.jsxまたはgatsby-browser.tsx作成して
使用するAPIをエクスポートする必要がある。

wrapRootElementを使う方法

GatsbyBrowserAPIのwrapRootElementを使ってpage間の状態維持をさせてみる。
wrapRootElementを使用すれば、pagesを含むルート要素をラップできる。

元にしているプログラムは下記記事でまとめたものを使ってます。

useContextを使ってContext用のコンポーネントを作成する

hooksのuseContextを使ってstateを保持するコンポーネントを作る。

src/context/index.jsx
import { createContext, useState, useContext } from "react";
// Context作成
const SampleContext = createContext();
// Contextを取得する関数を作成する
const useSampleContext = () => useContext(SampleContext);
// 引数の要素をproviderでラップするコンポーネント
const SampleProvider = ({ children }) => {
// state
const [count, setCount] = useState(1);
// stateを加算
const add = () => {
setCount((prevCount) => prevCount + 1);
};
// stateを減算
const substract = () => {
setCount((prevCount) => (prevCount !== 0 ? prevCount - 1 : prevCount));
};
// Contextに設定するオブジェクトに値を設定
const contextValue = {
count,
setCount,
add,
substract
};
return (
// 作成したContextのproviderでラップする
<SampleContext.Provider value={contextValue}>
{children}
</SampleContext.Provider>
);
};
// 関数とコンポーネントをexportしておく
export { useSampleContext, SampleProvider };

useContextの使い方については下記記事で紹介しています

gatsby-browser.jsでwrapRootElementをexportする

wrapRootElementをexportすると全てコンポーネントをラップすることができるので
ここで上記で作成したContext用のコンポーネントを使う

gatsby-browser.js
import React from "react"
import { SampleProvider } from "./src/context"
export const wrapRootElement = ({ element }) => {
console.log("wrapRootElement")
return <SampleProvider>{element}</SampleProvider>
}

Context用のコンポーネント内のstateは全てのコンポーネントで共有できる

gatsby-ssr.jsでwrapRootElementをexportする

GatsbyBrowserAPIのドキュメントにはgatsby-browser.jsとgatsby-ssr.jsの
両方に同じ実装をする必要がある注釈があるのでgatsby-ssr.jsも実装する。
※コピペでok

gatsby-ssr.js
import React from "react"
import { SampleProvider } from "./src/context"
export const wrapRootElement = ({ element }) => {
console.log("wrapRootElement")
return <SampleProvider>{element}</SampleProvider>
}

Gatsby ブラウザー API

stateを共有したコンポーネントを実装する

stateを共有させるpageコンポーネント、templateコンポーネントを作る

pageコンポーネント

pages/index.jsxで共有するstateの表示と更新ができように実装する

pages/index.jsx
import * as React from "react"
import { Link, graphql } from "gatsby"
import { useSampleContext } from "../context"
import { css } from "@emotion/react"
const BlogIndex = ({ data }) => {
const nodeList = data.allContentfulBlogPost.edges
//共有しているstateと関数を取得
const { count, add, substract } = useSampleContext()
return (
<div>
{/* 共有stateの表示と更新ボタン */}
<div css={[btnStyle]}>
contextのstate: {count}
<button onClick={add} css={[btnStyle]}>
加算
</button>
<button onClick={substract} css={[btnStyle]}>
減算
</button>
</div>
{nodeList.map(({ node }) => {
return (
<div>
<Link to={node.slug}>{node.title}</Link>
</div>
)
})}
</div>
)
}
export default BlogIndex
export const pageQuery = graphql`
query {
allContentfulBlogPost {
edges {
node {
title
slug
}
}
}
}
`
const btnStyle = () => [
css`
margin: 20px;
`,
]

画面はこんな感じ
上部にcontextのstateとstateの加算と減算ボタンを表示する

2022-10-27-23-53-35

遷移先のpageコンポーネントとしてaboutも作る
pages/index.jsxと同様に共有するstateの表示と更新ができように実装する

pages/about.jsx
import * as React from "react"
import { Link, graphql } from "gatsby"
import { useSampleContext } from "../context"
import { css } from "@emotion/react"
const About = ({ data }) => {
//共有しているstateと関数を取得
const { count, add, substract } = useSampleContext()
return (
<div>
<Link to="/">Home</Link>
{/* 共有stateの表示と更新ボタン */}
<div css={[btnStyle]}>
contextのstate: {count}
<button onClick={add} css={[btnStyle]}>
加算
</button>
<button onClick={substract} css={[btnStyle]}>
減算
</button>
</div>
<h1>About</h1>
</div>
)
}
export default About
const btnStyle = () => [
css`
margin: 20px;
`,
]

画面はこんな感じ
上部にcontextのstateとstateの加算と減算ボタンを表示する

2022-10-27-23-53-53

templateコンポーネント

同様にtemplate/contentfulPost.jsxで共有するstateの表示と更新ができように実装する

template/contentfulPost.jsx
import React from "react"
import { graphql, Link } from "gatsby"
import MDXConvert from "../components/mdxConvert"
import { useSampleContext } from "../context"
import { css } from "@emotion/react"
const ContentfulPost = ({ data }) => {
const post = data.allContentfulBlogPost.edges[0].node
const { body } = post
//共有しているstateと関数を取得
const { count, add, substract } = useSampleContext()
return (
<div>
<Link to="/">Home</Link>
{/* 共有stateの表示と更新ボタン */}
<div css={[btnStyle]}>
contextのstate: {count}
<button onClick={add} css={[btnStyle]}>
加算
</button>
<button onClick={substract} css={[btnStyle]}>
減算
</button>
</div>
<MDXConvert>{body.childMdx.body}</MDXConvert>
</div>
)
}
export default ContentfulPost
// gatsby-node.jsのcreatePageから渡されたslugを元にGraphQLで記事を取得する
export const query = graphql`
query Query($slug: String!) {
allContentfulBlogPost(filter: { slug: { eq: $slug } }) {
edges {
node {
body {
childMdx {
body
}
}
}
}
}
}
`
const btnStyle = () => [
css`
margin: 20px;
`,
]

画面はこんな感じ
上部にcontextのstateとstateの加算と減算ボタンを表示する

2022-10-27-23-54-21

実際に動かしてみる。

Gatsbyを起動して実際に動作を確認する。

useState2

page遷移を行っても共有しているstateが維持されていることが確認できる。

wrapPageElementを使う方法

wrapPageElementもwrapRootElementと同様にpage遷移時にアンマウントされないため
wrapRootElementの箇所をwrapPageElementに変えてもpage間でstateを共有させることができる。
※wrapPageElementの場合、再レンダリングは動く。

gatsby-browser.jsでwrapPageElementをexportする

wrapPageElementをexportするとpageコンポーネントをラップすることができる。

gatsby-browser.js
import React from "react"
import { SampleProvider } from "./src/context"
export const wrapPageElement = ({ element }) => {
console.log("wrapPageElement")
return <SampleProvider>{element}</SampleProvider>
}

Context用のコンポーネント内のstateは全てのコンポーネントで共有できる

gatsby-ssr.jsでwrapPageElementをexportする

コピペでok

gatsby-ssr.js
import React from "react"
import { SampleProvider } from "./src/context"
export const wrapPageElement = ({ element }) => {
console.log("wrapPageElement")
return <SampleProvider>{element}</SampleProvider>
}

gatsby-browser.jsとgatsby-ssr.jsのwrapRootElementをwrapPageElementに
置き換えて実行すれば、同じ動作をすることが確認できる。

wrapRootElementとwrapPageElementのどちらを使うべきか?

ドキュメントのwrapRootElementの説明を見ると下記のようにあるので

This is useful to set up any Provider components that will wrap your application.

Gatsby ブラウザー API

useContextや各種providerなどを使用するのはwrapRootElementの方がいいかと思います。

まとめ

Gatsbyでpage間でstateを共有する方法としてGatsbyBrowserAPIsのwrapRootElementを
使用するのがよさそうだとわかった。 darkmodeのflgなどpageを跨いでstateを保持させる場合などに使えそうです。

参考書籍

参考

新着記事

タグ一覧
top