当サイトは、アフィリエイト広告を利用しています
Gatsbyでは通常はページ遷移をした場合、pageコンポーネントがレンダリングされるため
stateを共有できなかった。
Gatsbyでpages間を跨いでstateを共有する
方法を調べてみたのでまとめておく。
具体的にはGatsbyBrowserAPIsの下記のいずれかを使う必要がある
上記に加えて、hooksのuseContextも利用する。
wrapRootElementとwrapPageElementのどちらでもpage間の状態維持は
可能だがwrapRootElementの方が適していると思う。
rootへの設定、グローバルに読み込ませたい設定などをすることができる
GatsbyのAPIのこと。
※wrapRootElementとwrapPageElementもGatsbyBrowserAPI
詳細は下記参照
GatsbyBrowserAPIを使用するためのファイル
GatsbyBrowserAPIを使用するにはプロジェクトルートに
gatsby-browser.jsxまたはgatsby-browser.tsx作成して
使用するAPIをエクスポートする必要がある。
GatsbyBrowserAPIのwrapRootElementを使ってpage間の状態維持をさせてみる。
wrapRootElementを使用すれば、pagesを含むルート要素をラップできる。
元にしているプログラムは下記記事でまとめたものを使ってます。
hooksのuseContextを使ってstateを保持するコンポーネントを作る。
import { createContext, useState, useContext } from "react";// Context作成const SampleContext = createContext();// Contextを取得する関数を作成するconst useSampleContext = () => useContext(SampleContext);// 引数の要素をproviderでラップするコンポーネントconst SampleProvider = ({ children }) => {// stateconst [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の使い方については下記記事で紹介しています
wrapRootElementをexportすると全てコンポーネントをラップすることができるので
ここで上記で作成したContext用のコンポーネントを使う
import React from "react"import { SampleProvider } from "./src/context"export const wrapRootElement = ({ element }) => {console.log("wrapRootElement")return <SampleProvider>{element}</SampleProvider>}
Context用のコンポーネント内のstateは全てのコンポーネントで共有できる
GatsbyBrowserAPIのドキュメントにはgatsby-browser.jsとgatsby-ssr.jsの
両方に同じ実装をする必要がある注釈があるのでgatsby-ssr.jsも実装する。
※コピペでok
import React from "react"import { SampleProvider } from "./src/context"export const wrapRootElement = ({ element }) => {console.log("wrapRootElement")return <SampleProvider>{element}</SampleProvider>}
stateを共有させるpageコンポーネント、templateコンポーネントを作る
pages/index.jsxで共有するstateの表示と更新ができように実装する
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 BlogIndexexport const pageQuery = graphql`query {allContentfulBlogPost {edges {node {titleslug}}}}`const btnStyle = () => [css`margin: 20px;`,]
画面はこんな感じ
上部にcontextのstateとstateの加算と減算ボタンを表示する
遷移先のpageコンポーネントとしてaboutも作る
pages/index.jsxと同様に共有するstateの表示と更新ができように実装する
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 Aboutconst btnStyle = () => [css`margin: 20px;`,]
画面はこんな感じ
上部にcontextのstateとstateの加算と減算ボタンを表示する
同様にtemplate/contentfulPost.jsxで共有するstateの表示と更新ができように実装する
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].nodeconst { 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の加算と減算ボタンを表示する
Gatsbyを起動して実際に動作を確認する。
page遷移を行っても共有しているstateが維持されていることが確認できる。
wrapPageElementもwrapRootElementと同様にpage遷移時にアンマウントされないため
wrapRootElementの箇所をwrapPageElementに変えてもpage間でstateを共有させることができる。
※wrapPageElementの場合、再レンダリングは動く。
wrapPageElementをexportするとpageコンポーネントをラップすることができる。
import React from "react"import { SampleProvider } from "./src/context"export const wrapPageElement = ({ element }) => {console.log("wrapPageElement")return <SampleProvider>{element}</SampleProvider>}
Context用のコンポーネント内のstateは全てのコンポーネントで共有できる
コピペでok
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の説明を見ると下記のようにあるので
This is useful to set up any Provider components that will wrap your application.
useContextや各種providerなどを使用するのはwrapRootElementの方がいいかと思います。
Gatsbyでpage間でstateを共有する方法としてGatsbyBrowserAPIsのwrapRootElementを
使用するのがよさそうだとわかった。
darkmodeのflgなどpageを跨いでstateを保持させる場合などに使えそうです。