Next.js 13のApp Routerでの状態管理(nrstate編)
前回は・・・
前回の Next.js 13のApp Routerでの状態管理 ではZustandとURLを利用し、Client Components間、Sever Components間で状態を共有しました。今回はnrstateを利用して同じ機能を実現してみます。
今回利用したフレームワーク/ライブラリー
- Next.js: 13.5.4
- nrstate: 1.0.2
- nrstate-client: 1.0.2
試しに作ってみたもの
前回と同じ機能になります。ただ、上記gifイメージを見て頂ければわかりますが、ボタン押下時に妙なブレ(?)がありますね・・・。
- AddボタンとResetボタンをClient Componentsとして作成しています
- その下のClient Component3にはボタン押下回数を表示してあり、Addボタン、Resetボタンと直接状態のやりとりはしておらず、nrstateを介して状態を共有しています
- 左側のServer Component1にも同じようにボタン押下回数を表示しています。前回ここはクエリーパラメーターを利用した状態の共有を自前で実装しましたが、今回はnrstateに任せました
コード
nrstate
nrstateとは?
State for React Server Components (RSC) on Next.js の頭文字を取って N(ext)R(eact Server Components)state
という名前のようで、正に探していたものという感じです。VTeacherの方が作られたようです。2023年5月10日以降修正されていない点が少し気になります。
nrstateの使い方
まず以下のようなファイルを作成します。
export const path = "/";
export type Counter = {
count: number;
};
export const initialCounter = { count: 0 } as Counter;
次に状態共有したいClient/Server Componentsの親を <PageStateProvider>
で囲います。
import { AddButton } from "@/components/client/AddButton";
import { Display } from "@/components/client/Display";
import { ResetButton } from "@/components/client/ResetButton";
import { SideMenu } from "@/components/server/Sidemenu";
import { Counter, initialCounter, path } from "@/store/Counter";
import { currentPageState } from "nrstate";
import PageStateProvider from "nrstate-client/PageStateProvider";
export default function Home() {
const count = 0;
return (
<PageStateProvider
current={currentPageState<Counter>(initialCounter, path)}
>
<main className="p-24">
<h2 className="text-xl">Server Component Page</h2>
<div className="flex mt-10">
<div>
<SideMenu />
</div>
<div className="ml-10"></div> {/* Spacer */}
<div>
<AddButton />
<div className="mt-10"></div> {/* Spacer */}
<ResetButton />
<div className="mt-10"></div> {/* Spacer */}
<Display />
</div>
</div>
</main>
</PageStateProvider>
);
}
Client Componentsでは usePageState<>()
を利用し、状態と状態を更新するための関数を取得します。
"use client";
import { Counter, path } from "@/store/Counter";
import { usePageState } from "nrstate-client";
export function AddButton() {
const [pageState, setPageState] = usePageState<Counter>();
const { count } = pageState;
const onClick = () => {
setPageState(
{
...pageState,
count: count + 1,
},
path
);
};
// 以降省略
}
Server Componentsでは getPageState<>()
を利用し、状態を取得します。
import { Counter, initialCounter, path } from "@/store/Counter";
import { getPageState } from "nrstate";
export function SideMenu() {
const pageState = getPageState<Counter>(initialCounter, path);
const { count } = pageState;
return (
<div className="border border-pink-400 p-10">
<h2 className="text-xl">Server Component1</h2>
<div className="mt-10">Count: {count}</div>
</div>
);
}
これだけです。自分でクエリーパラメーターを操作したり、ページ遷移する処理を書く必要もありません。簡単!
おそらく内部的にはクエリーパラメーターを利用して状態を共有しているのだと思います。ボタンを押すと http://localhost:3000/?_rsc=3z8t5
にGETが送られていたので。Counter.tsで設定した path
もこれに利用されていると想像します。
今後Zustandとnrstateのどちらを利用していくか?
利用者の多さや開発の活発さを考えると当面はZustandを利用していくと思います。Server Componentsに状態を共有する必要がある場合は、その部分のみ切り出しClient Componentsにするのが現時点では無難な気がします。
Server ActionsがStableになったり、Zustand(またはその他のライブラリー)がClient Componentsで変更した状態をServer Componentsに反映する機能を持つようになれば、そのときに改めて設計を検討するつもりです。
Discussion