restate / v1.0.0
コンポーネントが所有する状態
ドキュメント、ダイアログ、リクエストごとに専用のストアを用意します。ストアはコンポーネントと一緒に作られ、アンマウント後に解放されます。
useRestateScope
ファクトリーはストアを宣言して返します。restate はその結果をレンダリングをまたいで保持し、コンポーネントのアンマウント後にスコープを破棄します。close 関数やクリーンアップ用の effect を書く必要はありません。
2 つの Document 要素はそれぞれ独立した状態を持ちます。ドキュメントを子コンポーネントと共有するには、返されたフックを props か独自のコンテキストで渡します。
import { restate, useRestateScope } from '@kkwaenggwari/restate';
function Document() {
const { useDraft, useSelection } = useRestateScope((scope) => ({
useDraft: restate('', { scope }),
useSelection: restate(0, { scope }),
}));
const text = useDraft();
const selection = useSelection();
return (
<section>
<input value={text} onChange={(event) => useDraft.set(event.target.value)} />
<button onClick={() => useSelection.set((n) => n + 1)}>Selection: {selection}</button>
</section>
);
}ファクトリーのルール
React はファクトリーを複数回呼び出して結果を捨てることがあるので、同期的で純粋に保ってください。
- ストアはファクトリーの中で宣言し、返されたフックはコンポーネントのトップレベルで呼び出します。
- 初期 props は useState の初期化関数と同じく、一度だけ取り込まれます。
- 別のドキュメントで新しい状態が必要なときは、コンポーネントに key を付けます。
- ファクトリーなしの useRestateScope() は、後から effect やハンドラーで作るストアのための安定したスコープを返します。
props から初期値を受け取る
props を初期値として使い、別のドキュメントでやり直すには React の key を使います。
function Editor({ document }) {
// The initial value is captured once, like a useState initializer.
const useTitle = useRestateScope((scope) => restate(document.title, { scope }));
const title = useTitle();
return <input value={title} onChange={(event) => useTitle.set(event.target.value)} />;
}
// A different document gets fresh state.
<Editor key={document.id} document={document} />;サーバーレンダリング
サーバーのルートでデータを読み込み、prop として渡します。初期値はサーバースナップショットにもなります。Next.js App Router では、このフックは Client Component に置きます。Client Component もサーバーでレンダリングされます。
'use client';
import { restate, useRestateScope } from '@kkwaenggwari/restate';
export default function Profile({ user }) {
const useProfile = useRestateScope((scope) => restate(user, { scope }));
const profile = useProfile();
return <p>{profile.name}</p>;
}ドキュメントの保存データを待つ
スコープ内の保存ストアは、クライアントでコンポーネントがコミットされてから復元を始め、グローバルな restateManager.ready には含まれません。ローディング境界で、そのスコープ自身の manager.ready を待ちます。
import { useEffect, useState } from 'react';
import { persistedRestate, useRestateScope } from '@kkwaenggwari/restate';
function Document({ documentId }) {
const { manager, useDraft } = useRestateScope((scope) => ({
manager: scope.manager,
useDraft: persistedRestate('', { scope, namespace: 'documents', key: documentId }),
}));
const [state, setState] = useState('loading');
useEffect(() => {
let active = true;
manager.ready.then(
() => active && setState('ready'),
() => active && setState('error'),
);
return () => {
active = false;
};
}, [manager]);
if (state === 'error') return <p>Could not load this document.</p>;
if (state === 'loading') return <p>Loading document…</p>;
return <Editor useDraft={useDraft} />;
}試してみる
ドキュメントを開いたり閉じたりして、状態が新しく始まる様子を見てください。
サンプルを準備中…
React の外にいる所有者
サービスやサーバーのリクエストが状態を所有する場合は、スコープを自分で作り、終わったら destroy() を呼びます。スコープを破棄しても、保存済みのレコードが削除されることはありません。
import { createRestateScope, persistedRestate } from '@kkwaenggwari/restate';
export function createDocument(documentId) {
const scope = createRestateScope();
const useDraft = persistedRestate('', {
scope,
namespace: 'documents',
key: documentId,
});
return {
useDraft,
manager: scope.manager,
save: () => scope.manager.saveAll(),
destroy: () => scope.destroy(),
};
}