restate / v1.0.0

コンポーネントが所有する状態

ドキュメント、ダイアログ、リクエストごとに専用のストアを用意します。ストアはコンポーネントと一緒に作られ、アンマウント後に解放されます。

このページの内容useRestateScopeファクトリーのルールprops から初期値を受け取るサーバーレンダリングドキュメントの保存データを待つ試してみるReact の外にいる所有者

useRestateScope

ファクトリーはストアを宣言して返します。restate はその結果をレンダリングをまたいで保持し、コンポーネントのアンマウント後にスコープを破棄します。close 関数やクリーンアップ用の effect を書く必要はありません。

2 つの Document 要素はそれぞれ独立した状態を持ちます。ドキュメントを子コンポーネントと共有するには、返されたフックを props か独自のコンテキストで渡します。

JavaScript / JSX
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 を使います。

JavaScript / JSX
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 もサーバーでレンダリングされます。

JavaScript / JSX
'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 を待ちます。

JavaScript / JSX
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() を呼びます。スコープを破棄しても、保存済みのレコードが削除されることはありません。

JavaScript / JSX
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(),
  };
}

ドキュメントを検索

すべてのガイドのタイトル、説明、本文から検索します。