履歴エントリに状態を置く
開いている行や広げたパネルのように、戻るボタンでは元に戻ってほしいけれど、リンクには載せたくない状態があります。こうした状態は、履歴エントリの隠れた面であるentryに置きます。
このページの内容
entryに書く
definePageStateにentryのスキーマを渡します。値はURLには出ず、Navigation APIが履歴エントリごとに持つ状態の中に、定義のキーを名前空間にして保存されます。
orders-state.tsimport { definePageState } from '@k8ordo/state';
import * as z from 'zod';
export const ordersState = definePageState('orders', {
entry: z.object({
expanded: z.array(z.string()).default([]),
showNotes: z.boolean().default(false),
}),
});値は文字列にされず、型の付いたまま保存されます。そのため、真偽値はz.boolean()で、数はz.number()で、書いたとおりの型のまま受けます。
ただし、スキーマは自分が出した値をもう一度受け付けなければなりません。書いた値は、型の付いたままスキーマを通り直すからです。z.stringbool()や型を変える変換は、書くたびに既定値へ戻ってしまいます。
サーバーには履歴エントリが無いので、サーバーの描画とハイドレーションの描画は既定値で行われます。
urlとentryを1つの定義にまとめる
1つの定義に、urlとentryの両方を書けます。useAppStateが返す状態は、2つを平らにまとめたものです。
export const ordersState = definePageState('orders', {
url: z.object({
status: z.enum(['all', 'pending', 'shipped']).default('all'),
}),
entry: z.object({
expanded: z.array(z.string()).default([]),
}),
});
const [{ status, expanded }, update] = useAppState(ordersState);フィールドをurlからentryへ移しても、変わるのは定義だけで、呼び出す側のコードはそのまま動きます。ただし綴りはフィールドについて回ります。urlでz.stringbool()だった真偽値は、entryではz.boolean()に書き換えます。
同じ名前のフィールドを両方に書くと、型エラーになります。実行時にも、"orders" declares in both url and entry: statusのように投げます。
2つの面は一度に書き込まれる
1回のupdate()で、urlとentryの両方を変えられます。2つの面は同じ履歴エントリにあるので、何が変わったかで書き込み方が決まります。
update({ status: 'pending' }, { history: 'push' });
navigation.navigate()が1回
update({ expanded: ['A-102'] });
updateCurrentEntry()で今のエントリを書き換えるurlの値が変わるとき:navigation.navigate()を1回呼び、新しいURLとエントリの状態を一緒に渡します。片方だけが変わった画面が見えることはありません。entryの値だけが変わるとき:navigation.updateCurrentEntry()で今のエントリを書き換えます。遷移を伴わないので、どのルーターの下でも動きます。{ history: 'push' }を付けても、新しいエントリは作られません。
URLを変える書き込みは、今のentryの値を新しいエントリへ持ち越します。エントリの状態にほかの定義の名前空間があれば、それもそのまま残ります。
戻る、進む、再読み込みで戻るもの
2つの面は、どちらもブラウザの履歴エントリに載っています。ライブラリが別に記録を取っているわけではないので、ブラウザの操作がそのまま両方に効きます。
- 戻る/進む:移った先のエントリにあった
urlとentryが、一緒に戻ります。 - 再読み込み:同じエントリのまま読み込み直すので、どちらも残ります。
- URLを新しいタブで開く:渡るのはURLだけです。
urlは同じ値になり、entryは既定値から始まります。 - リンクのクリック:新しいエントリには
entryの値がまだ無いので、既定値から始まります。 - セッションの復元:古いスキーマが書いた値が戻ってくることもあります。スキーマに合わない値は、そのフィールドだけが既定値に戻ります。
2つの面を見比べる
このページのURLと履歴エントリを実際に書き換える、本物のdefinePageStateです。絞り込みはurlに、開いている注文はentryに置いています。
- 発送済み
- 未発送
- 未発送
- url
クエリなし- entry
{"expanded":[]}
試してみる
- 「A-102」を開きます。
entryの値は変わりますが、URLのクエリは変わりません。 - 絞り込みを「未発送」に切り替えます。クエリが
?status=pendingになり、「A-102」は開いたまま残ります。 - 「A-103」も開いてから、ブラウザの戻るを押します。絞り込みが「すべて」に戻り、開いている注文も「A-102」だけに戻ります。
- ページを再読み込みしても、絞り込みと開いている注文はそのまま残ります。