restate / v1.0.0
保存は自分のタイミングで
ストアを IndexedDB や MMKV に保存し、起動時に一度だけ読み込み、明示的または自動で保存して、未保存の変更を表示します。
保存ストアを宣言する
restate を persistedRestate に置き換えて key を渡します。コンポーネントは同じフックと set をそのまま使います。
保存された値は作成後に自動で復元されます。編集で変わるのはメモリだけで、保存するか autoSave をオンにするまで何も書き込まれません。
import { persistedRestate } from '@kkwaenggwari/restate';
export const useDraft = persistedRestate('', {
key: 'article-draft',
validate: (value) => typeof value === 'string',
});起動時に一度だけ待つ
クライアントのエントリーでアプリとストアを import し、描画の前に restateManager.ready を待ちます。そうすればコンポーネントは保存された値をそのまま読めます。
サーバーレンダリング中やブラウザーにブロックされているときなど、ストレージが使えないことは失敗ではありません。そうしたストアはメモリ上で動きます。ready が reject されるのは、レコードを読み込めない、マイグレーションできない、または検証を通らないときだけです。
import { createRoot } from 'react-dom/client';
import { restateManager } from '@kkwaenggwari/restate';
import App from './App';
async function start() {
const root = createRoot(document.getElementById('root'));
try {
await restateManager.ready;
root.render(<App />);
} catch (error) {
root.render(<p>Could not load saved data.</p>);
console.error(error);
}
}
start();保存、元に戻す、リセット
save は現在の値を書き込み、restore は保存済みの値に戻し、resetAll はメモリとストレージの両方をクリアします。どれも Promise を返します。
await useDraft.save(); // write the current value
await useDraft.restore(); // return to the saved value
await useDraft.resetAndClearStorage(); // initial value in memory, record removed未保存の変更と進行状況を表示する
useStatus は永続化のステータスが変わると再レンダリングされます。dirty は、メモリが最後に読み込み・保存・復元した値と異なる間 true です。operation は実行中のストレージ操作の名前を示します。
1 つのフィールドが変わったときだけ再レンダリングするには、selectorを渡します。
function SaveButton() {
const { dirty, operation, error } = useDraft.useStatus();
const saving = operation === 'save';
return (
<>
<button disabled={!dirty || saving} onClick={() => useDraft.save().catch(() => {})}>
{saving ? 'Saving…' : 'Save'}
</button>
{error != null && <p>Could not save your draft.</p>}
</>
);
}試してみる
入力し、ストレージの遅延を大きくして保存します。サンプルをもう一度再生すると、保存した下書きが復元されます。
サンプルを準備中…
自動で保存する
設定や下書きのように常に保存したい値は、一度オプトインするだけです。autoSave: true は変更のたびに保存し、{ delayMs } は変更が止まるまで待ちます。
連続した変更は 1 回の書き込みになり、保存済みの値に戻す変更では何も書き込みません。失敗は status().error に表示されます。コンポーネントが所有するスコープは、アンマウント時に保留中の変更を書き込みます。
export const useTheme = persistedRestate('light', { key: 'theme', autoSave: true });
export const useDraft = persistedRestate('', {
key: 'article-draft',
autoSave: { delayMs: 500 },
});ページを閉じる前に
同じステータスを React の外でも読めます。未保存の変更があるときは離れる前に警告したり、ページが非表示になったときにすべて保存したりできます。
window.addEventListener('beforeunload', (event) => {
if (useDraft.status().dirty) event.preventDefault();
});
window.addEventListener('pagehide', () => {
restateManager.saveAll();
});ストレージのエラーを処理する
ストレージのエラーは、その操作の Promise を reject します。restate のエラーにはすべて code があり、保存ストアの場合はレコードの key も付きます。
try {
await useDraft.save();
} catch (error) {
if (error.code === 'STORAGE_UNAVAILABLE') showNotice('Saving is off in this browser.');
else if (error.code === 'STORAGE_VALUE_TOO_LARGE') showNotice('This draft is too large.');
else showNotice(`Could not save "${error.key ?? 'draft'}".`);
}