State für React und React Native · 1.0
State teilen.Ein Hook.Fertig.
Deklariere einen Store in einer Zeile und nutze ihn in jeder Komponente – ohne Provider, Actions oder Reducer. Eine Komponente rendert nur, wenn sich der ausgewählte Wert ändert, und gespeichert wird, wann du willst.
npm install @kkwaenggwari/restateLive — ein echter restate-Store
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))items und total ändern sich – drei Karten rendern, die Nachricht bleibt ruhig.
- Einfach0Provider, Actions und Reducer einzurichtenWert deklarieren und nutzen. Vorher muss nichts verdrahtet werden.
- Schnell11 → 1Elf Änderungen, ein SchreibvorgangautoSave wartet auf eine Pause, daher werden elf schnelle Änderungen zu einem einzigen Schreibvorgang.
- Stabil26Typisierte FehlercodesJeder Fehler sagt, was schiefging und welchen Datensatz er betraf.
01 — Einfach
Dieselbe Funktion, weniger Konzepte.
Vier Implementierungen derselben Aufgabe nebeneinander. Die Zahlen stammen aus dem hier gezeigten Code und werden beim Build dieser Website gemessen.
Geteilter Zähler
Zwei Komponenten lesen und aktualisieren eine Zahl.
Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.
Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.
Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';
const slice = createSlice({
name: 'count',
initialState: 0,
reducers: { increment: (count) => count + 1 },
});
const store = configureStore({
reducer: { count: slice.reducer },
});
function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return <button onClick={() => dispatch(slice.actions.increment())}>{count}</button>;
}
export default function App() {
return (
<Provider store={store}>
<Counter />
<Counter />
</Provider>
);
}import { create } from 'zustand';
const useCount = create(() => ({ count: 0 }));
const increment = () =>
useCount.setState((s) => ({
count: s.count + 1,
}));
function Counter() {
const count = useCount((s) => s.count);
return <button onClick={increment}>{count}</button>;
}
export default function App() {
return (
<>
<Counter />
<Counter />
</>
);
}import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return <button onClick={() => setCount((n) => n + 1)}>{count}</button>;
}
export default function App() {
return (
<>
<Counter />
<Counter />
</>
);
}restate Eine Deklaration ist zugleich Store und Hook.
import { restate } from '@kkwaenggwari/restate';
const useCount = restate(0);
function Counter() {
const count = useCount();
return <button onClick={() => useCount.set((n) => n + 1)}>{count}</button>;
}
export default function App() {
return (
<>
<Counter />
<Counter />
</>
);
}Für diese Aufgabe sind auch Zustand und Jotai klein und ohne Provider. Der Abstand wächst, sobald Speichern und State pro Dokument ins Spiel kommen.
Ein Feld auswählen
Die Titelkomponente darf nicht rendern, wenn sich nur der Text ändert.
Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.
Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.
Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useSelector } from 'react-redux';
const article = createSlice({
name: 'article',
initialState: { title: 'Hello', body: '' },
reducers: {
editBody: (i) => {
i.body += '!';
},
editTitle: (i) => {
i.title += '!';
},
},
});
const store = configureStore({ reducer: article.reducer });
function Title() {
const title = useSelector((i) => i.title);
return <h3>{title}</h3>;
}
export default function App() {
return (
<Provider store={store}>
<Title />
<button onClick={() => store.dispatch(article.actions.editBody())}>body + !</button>
<button onClick={() => store.dispatch(article.actions.editTitle())}>title + !</button>
</Provider>
);
}import { create } from 'zustand';
const useArticle = create(() => ({ title: 'Hello', body: '' }));
function Title() {
const title = useArticle((i) => i.title);
return <h3>{title}</h3>;
}
export default function App() {
return (
<>
<Title />
<button
onClick={() =>
useArticle.setState((i) => ({
body: i.body + '!',
}))
}
>
body + !
</button>
<button
onClick={() =>
useArticle.setState((i) => ({
title: i.title + '!',
}))
}
>
title + !
</button>
</>
);
}import { atom, useAtomValue, useSetAtom } from 'jotai';
const articleAtom = atom({ title: 'Hello', body: '' });
const titleAtom = atom((get) => get(articleAtom).title);
function Title() {
const title = useAtomValue(titleAtom);
return <h3>{title}</h3>;
}
export default function App() {
const setArticle = useSetAtom(articleAtom);
return (
<>
<Title />
<button
onClick={() =>
setArticle((i) => ({
...i,
body: i.body + '!',
}))
}
>
body + !
</button>
<button
onClick={() =>
setArticle((i) => ({
...i,
title: i.title + '!',
}))
}
>
title + !
</button>
</>
);
}restate Übergib einen selector an denselben Hook, den du schon nutzt.
import { restate } from '@kkwaenggwari/restate';
const useArticle = restate({ title: 'Hello', body: '' });
function Title() {
const title = useArticle((i) => i.title);
return <h3>{title}</h3>;
}
export default function App() {
return (
<>
<Title />
<button
onClick={() =>
useArticle.set((i) => ({
...i,
body: i.body + '!',
}))
}
>
body + !
</button>
<button
onClick={() =>
useArticle.set((i) => ({
...i,
title: i.title + '!',
}))
}
>
title + !
</button>
</>
);
}Jedes Tool hier kann auswählen. restate nutzt denselben Hook-Aufruf statt eines eigenen selector-Hooks oder eines abgeleiteten Atoms.
Einen Entwurf speichern
Beim Start einen gespeicherten Entwurf wiederherstellen, beim Klick speichern und die Zustände „speichert“, „nicht gespeichert“ und „fehlgeschlagen“ anzeigen.
Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.
Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.
Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.
import { useEffect, useState } from 'react';
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useSelector } from 'react-redux';
function openDraftDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('draft-example', 1);
let blocked = false;
request.onupgradeneeded = () => {
request.result.createObjectStore('drafts');
};
request.onblocked = () => {
blocked = true;
reject(new Error('Close other tabs to upgrade storage'));
};
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
if (blocked) {
db.close();
return;
}
db.onversionchange = () => db.close();
resolve(db);
};
});
}
async function storedDraft(mode, text) {
const db = await openDraftDB();
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction('drafts', mode);
const store = tx.objectStore('drafts');
const request = mode === 'readonly' ? store.get('draft') : store.put(text, 'draft');
tx.oncomplete = () => resolve(request.result);
tx.onabort = () => reject(tx.error || new Error('Storage aborted'));
});
} finally {
db.close();
}
}
const draft = createSlice({
name: 'draft',
initialState: '',
reducers: { edit: (_, action) => action.payload },
});
const store = configureStore({ reducer: { draft: draft.reducer } });
const hydrated = storedDraft('readonly').then((saved) => {
if (saved === undefined) return;
if (typeof saved !== 'string') throw new Error('Invalid draft');
const value = saved;
store.dispatch(draft.actions.edit(value));
});
// Observe errors immediately, even before the component mounts.
hydrated.catch(() => {});
function DraftEditor() {
const text = useSelector((state) => state.draft);
const [ready, setReady] = useState(false);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState('Loading');
useEffect(() => {
let active = true;
hydrated.then(
() => {
if (active) {
setReady(true);
setMessage('Ready');
}
},
() => {
if (active) setMessage('Storage error');
},
);
return () => {
active = false;
};
}, []);
async function save() {
setSaving(true);
try {
await storedDraft('readwrite', store.getState().draft);
setMessage('Saved');
} catch {
setMessage('Save failed');
} finally {
setSaving(false);
}
}
return (
<>
<label>
Draft
<textarea
value={text}
disabled={!ready || saving}
onChange={(event) => {
const value = event.target.value;
store.dispatch(draft.actions.edit(value));
setMessage('Edited');
}}
/>
</label>
<output>{text}</output>
<button disabled={!ready || saving} onClick={save}>
Save
</button>
<p>{message}</p>
</>
);
}
export default function App() {
return (
<Provider store={store}>
<DraftEditor />
</Provider>
);
}import { useEffect, useState } from 'react';
import { create } from 'zustand';
function openDraftDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('draft-example', 1);
let blocked = false;
request.onupgradeneeded = () => {
request.result.createObjectStore('drafts');
};
request.onblocked = () => {
blocked = true;
reject(new Error('Close other tabs to upgrade storage'));
};
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
if (blocked) {
db.close();
return;
}
db.onversionchange = () => db.close();
resolve(db);
};
});
}
async function storedDraft(mode, text) {
const db = await openDraftDB();
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction('drafts', mode);
const store = tx.objectStore('drafts');
const request = mode === 'readonly' ? store.get('draft') : store.put(text, 'draft');
tx.oncomplete = () => resolve(request.result);
tx.onabort = () => reject(tx.error || new Error('Storage aborted'));
});
} finally {
db.close();
}
}
const useDraft = create(() => ({ text: '' }));
const hydrated = storedDraft('readonly').then((saved) => {
if (saved === undefined) return;
if (typeof saved !== 'string') throw new Error('Invalid draft');
const value = saved;
useDraft.setState({ text: value });
});
// Observe errors immediately, even before the component mounts.
hydrated.catch(() => {});
function DraftEditor() {
const text = useDraft((state) => state.text);
const [ready, setReady] = useState(false);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState('Loading');
useEffect(() => {
let active = true;
hydrated.then(
() => {
if (active) {
setReady(true);
setMessage('Ready');
}
},
() => {
if (active) setMessage('Storage error');
},
);
return () => {
active = false;
};
}, []);
async function save() {
setSaving(true);
try {
await storedDraft('readwrite', useDraft.getState().text);
setMessage('Saved');
} catch {
setMessage('Save failed');
} finally {
setSaving(false);
}
}
return (
<>
<label>
Draft
<textarea
value={text}
disabled={!ready || saving}
onChange={(event) => {
const value = event.target.value;
useDraft.setState({ text: value });
setMessage('Edited');
}}
/>
</label>
<output>{text}</output>
<button disabled={!ready || saving} onClick={save}>
Save
</button>
<p>{message}</p>
</>
);
}
export default function App() {
return <DraftEditor />;
}import { useEffect, useState } from 'react';
import { atom, createStore, useAtomValue } from 'jotai';
function openDraftDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('draft-example', 1);
let blocked = false;
request.onupgradeneeded = () => {
request.result.createObjectStore('drafts');
};
request.onblocked = () => {
blocked = true;
reject(new Error('Close other tabs to upgrade storage'));
};
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
if (blocked) {
db.close();
return;
}
db.onversionchange = () => db.close();
resolve(db);
};
});
}
async function storedDraft(mode, text) {
const db = await openDraftDB();
try {
return await new Promise((resolve, reject) => {
const tx = db.transaction('drafts', mode);
const store = tx.objectStore('drafts');
const request = mode === 'readonly' ? store.get('draft') : store.put(text, 'draft');
tx.oncomplete = () => resolve(request.result);
tx.onabort = () => reject(tx.error || new Error('Storage aborted'));
});
} finally {
db.close();
}
}
const draftAtom = atom('');
const store = createStore();
const hydrated = storedDraft('readonly').then((saved) => {
if (saved === undefined) return;
if (typeof saved !== 'string') throw new Error('Invalid draft');
const value = saved;
store.set(draftAtom, value);
});
// Observe errors immediately, even before the component mounts.
hydrated.catch(() => {});
function DraftEditor() {
const text = useAtomValue(draftAtom, { store });
const [ready, setReady] = useState(false);
const [saving, setSaving] = useState(false);
const [message, setMessage] = useState('Loading');
useEffect(() => {
let active = true;
hydrated.then(
() => {
if (active) {
setReady(true);
setMessage('Ready');
}
},
() => {
if (active) setMessage('Storage error');
},
);
return () => {
active = false;
};
}, []);
async function save() {
setSaving(true);
try {
await storedDraft('readwrite', store.get(draftAtom));
setMessage('Saved');
} catch {
setMessage('Save failed');
} finally {
setSaving(false);
}
}
return (
<>
<label>
Draft
<textarea
value={text}
disabled={!ready || saving}
onChange={(event) => {
const value = event.target.value;
store.set(draftAtom, value);
setMessage('Edited');
}}
/>
</label>
<output>{text}</output>
<button disabled={!ready || saving} onClick={save}>
Save
</button>
<p>{message}</p>
</>
);
}
export default function App() {
return <DraftEditor />;
}restate persistedRestate stellt automatisch wieder her, und useStatus meldet Speichervorgang, ungespeicherte Änderungen und Fehler.
import { persistedRestate } from '@kkwaenggwari/restate';
const useDraft = persistedRestate('', {
key: 'draft',
validate: (value) => typeof value === 'string',
});
export default function App() {
const text = useDraft();
const { dirty, operation, error } = useDraft.useStatus();
const saving = operation === 'save';
const message = error ? 'Save failed' : saving ? 'Saving…' : dirty ? 'Edited' : 'Saved';
return (
<>
<label>
Draft
<textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />
</label>
<output>{text}</output>
<button disabled={saving} onClick={() => useDraft.save().catch(() => {})}>
Save
</button>
<p>{message}</p>
</>
);
}Die anderen Versionen schreiben denselben IndexedDB-Code von Hand. Ihre Persistenz-Middleware oder Speicher-Utilities können ihn mit einem Adapter verkürzen; dieser Code wird hier nicht gezählt.
State pro Dokument
Zwei Dokumente behalten unabhängigen State, und das Schließen eines Dokuments gibt ihn frei.
Redux Toolkit Hält Updates in Slices und Actions, die an einen per React-Context bereitgestellten Store dispatcht werden. Hervorragende DevTools und ein strikter, vorhersehbarer Update-Fluss für große Teams.
Zustand Erstellt mit create() einen Store-Hook und ändert ihn mit setState. Winzig und flexibel, mit Middleware für Persistenz und DevTools.
Jotai Kombiniert Atoms und abgeleitete Atoms, optional innerhalb eines Providers. Feingranulare Abhängigkeiten zwischen vielen kleinen State-Stücken.
import { useState } from 'react';
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useSelector } from 'react-redux';
const draft = createSlice({
name: 'draft',
initialState: '',
reducers: { edit: (_, action) => action.payload },
});
const selectionSlice = createSlice({
name: 'selection',
initialState: 0,
reducers: { next: (index) => index + 1 },
});
function createDocument() {
return {
store: configureStore({
reducer: {
draft: draft.reducer,
selection: selectionSlice.reducer,
},
}),
};
}
function Document({ doc, name }) {
const text = useSelector((state) => state.draft);
const selection = useSelector((state) => state.selection);
return (
<section>
<label>
{name}
<input
value={text}
onChange={(event) => doc.store.dispatch(draft.actions.edit(event.target.value))}
/>
</label>
<output>{text}</output>
<button onClick={() => doc.store.dispatch(selectionSlice.actions.next())}>
Selection: {selection}
</button>
</section>
);
}
function DocumentSlot({ name }) {
const [doc] = useState(createDocument);
return (
<Provider store={doc.store}>
<Document doc={doc} name={name} />
</Provider>
);
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <DocumentSlot name="Left" />}
<DocumentSlot name="Right" />
</>
);
}import { useState } from 'react';
import { useStore } from 'zustand';
import { createStore } from 'zustand/vanilla';
function createDocument() {
return {
draft: createStore(() => ({ text: '' })),
selection: createStore(() => ({ index: 0 })),
};
}
function Document({ doc, name }) {
const text = useStore(doc.draft, (state) => state.text);
const selection = useStore(doc.selection, (state) => state.index);
return (
<section>
<label>
{name}
<input
value={text}
onChange={(event) => doc.draft.setState({ text: event.target.value })}
/>
</label>
<output>{text}</output>
<button onClick={() => doc.selection.setState((s) => ({ index: s.index + 1 }))}>
Selection: {selection}
</button>
</section>
);
}
function DocumentSlot({ name }) {
const [doc] = useState(createDocument);
return <Document doc={doc} name={name} />;
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <DocumentSlot name="Left" />}
<DocumentSlot name="Right" />
</>
);
}import { useState } from 'react';
import { atom, createStore, useAtomValue } from 'jotai';
const draftAtom = atom('');
const selectionAtom = atom(0);
function createDocument() {
return { store: createStore() };
}
function Document({ doc, name }) {
const text = useAtomValue(draftAtom, { store: doc.store });
const selection = useAtomValue(selectionAtom, { store: doc.store });
return (
<section>
<label>
{name}
<input value={text} onChange={(event) => doc.store.set(draftAtom, event.target.value)} />
</label>
<output>{text}</output>
<button onClick={() => doc.store.set(selectionAtom, (n) => n + 1)}>
Selection: {selection}
</button>
</section>
);
}
function DocumentSlot({ name }) {
const [doc] = useState(createDocument);
return <Document doc={doc} name={name} />;
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <DocumentSlot name="Left" />}
<DocumentSlot name="Right" />
</>
);
}restate useRestateScope erstellt die Stores und zerstört sie nach dem Unmount.
import { useState } from 'react';
import { restate, useRestateScope } from '@kkwaenggwari/restate';
function Document({ name }) {
const doc = useRestateScope((scope) => ({
useDraft: restate('', { scope }),
useSelection: restate(0, { scope }),
}));
const text = doc.useDraft();
const selection = doc.useSelection();
return (
<section>
<label>
{name}
<input value={text} onChange={(event) => doc.useDraft.set(event.target.value)} />
</label>
<output>{text}</output>
<button onClick={() => doc.useSelection.set((n) => n + 1)}>Selection: {selection}</button>
</section>
);
}
export default function App() {
const [leftOpen, setLeftOpen] = useState(true);
return (
<>
<button onClick={() => setLeftOpen((open) => !open)}>
{leftOpen ? 'Close left' : 'Open left'}
</button>
{leftOpen && <Document name="Left" />}
<Document name="Right" />
</>
);
}Ein Jotai-Provider oder -Store und Vanilla-Stores von Zustand können State ebenfalls isolieren; du erstellst und verbindest sie selbst.
So werden diese Zahlen gezählt
Nicht leere Zeilen des gezeigten Codes und die aus jeder State-Bibliothek importierten Namen (React-Imports werden nicht gezählt). Jede Version rendert dieselbe UI mit demselben Speicher und denselben Lade- und Fehlerzuständen. Die restate-Beispiele setzen await restateManager.ready am Einstiegspunkt der App voraus. Die Quellen wurden anhand der offiziellen Dokumentation des jeweiligen Projekts geprüft:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Wähle es, wenn ein einziges prüfbares Action-Log und ein großes Middleware-Ökosystem am wichtigsten sind
- Zustand — Zustand · Introduction, Zustand · persist. Wähle es, wenn du den kleinstmöglichen Kern willst und Middleware lieber selbst kombinierst
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Wähle es, wenn dein State von Natur aus ein Graph kleiner abgeleiteter Werte ist
02 — Schnell
Nur rendern, was sich geändert hat. Nur einmal schreiben.
Beide Panels führen das echte restate-Paket in deinem Browser aus. Klick, tipp und beobachte die Zähler.
Render-Karte
Dieselben vier Felder, auf zwei Arten gelesen. Ändere ein Feld und zähle die Renders.
- Name
- Theme
- Zähler
- Entwurf
- Name
- Theme
- Zähler
- Entwurf
Zusammengeführte Schreibvorgänge
autoSave wartet auf eine Tipppause und schreibt dann einmal. Eine Salve von save()-Aufrufen teilt sich einen Schreibvorgang in der Warteschlange.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });Die Zahlen stammen aus echten React-Renders und einem zählenden Speicheradapter auf dieser Seite. Auch andere Bibliotheken können Werte auswählen; hier ist Auswählen einfach die Art zu lesen.
03 — Stabil
Wenn der Speicher versagt, bleiben deine Daten erhalten.
Beim Speichern geht State verloren: langsame Datenträger, blockierter Speicher, geschlossene Tabs. Das hier sind Garantien von restate 1.0, keine Ratschläge.
- 01
Speichervorgänge laufen der Reihe nach
Jeder Store führt seine Speicheroperationen nacheinander aus, daher kann ein langsamer Speichervorgang nie einen neueren Wert mit einem älteren überschreiben.
save() · restore() - 02
Ein wartender Speichervorgang nimmt den neuesten Wert
Ein save()-Aufruf, während ein anderer Speichervorgang in der Warteschlange wartet, schließt sich diesem an, statt weitere Schreibvorgänge zu stapeln.
save(): Promise<T> - 03
Ungelesene Datensätze werden nie versehentlich überschrieben
Wenn ein Datensatz nicht geladen werden konnte, lassen autoSave und Sammel-Speichervorgänge ihn in Ruhe und melden LOAD_FAILED. Nur ein explizites save() darf ihn ersetzen.
LOAD_FAILED - 04
Unmount – und das Speichern wird trotzdem fertig
Vor dem Unmount angeforderte Speichervorgänge werden abgeschlossen. Spätere Schreibvorgänge in einen zerstörten Store werden ignoriert, wie React-State nach dem Unmount.
useRestateScope - 05
Kein Speicher? Die App startet trotzdem
Beim Server-Rendering oder bei blockiertem Speicher arbeiten Stores im Arbeitsspeicher weiter und melden, dass der Speicher nicht verfügbar ist.
status().available - 06
Fehler sagen, was und wo
Jeder restate-Fehler trägt einen typisierten Code und den Key seines Datensatzes, damit du die passende Meldung anzeigen kannst.
error.code · error.key
Wächst mit deiner App
Ändere die Deklaration. Behalte deine Komponenten.
Speichern, automatisches Speichern und Validierung sind Optionen desselben Stores. Die Komponente rechts ändert sich nie.
Arbeitsspeicher
Ein geteilter Wert im Arbeitsspeicher.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Gespeichert
Wird automatisch wiederhergestellt; geschrieben wird beim Aufruf von save().
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Automatisch
Wird einmal geschrieben, nachdem das Tippen 500 ms pausiert.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });Validiert
Ältere Datensätze werden vor der Nutzung migriert und geprüft.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, version: 2, migrate: (value) => String(value ?? ''), validate: (value) => typeof value === 'string', });
Komponente — in jedem Schritt unverändert
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}Läuft überall, wo React läuft
Ein Paket für jede React-Umgebung.
react >=16.8.0React
Funktioniert mit React 16.8 und neuer, im Browser und beim Server-Rendering.
react-native-mmkv >=2.0.0React Native
Dieselben Imports. Gespeichert wird mit MMKV, sobald react-native-mmkv installiert ist.
SSRServer-Rendering
Komponenteneigene Stores übernehmen ihren Server-Snapshot aus den Props und beginnen nach der Hydration zu laden.
index.d.tsTypeScript
Deklarationen inklusive. Selektoren, Statusfelder und Fehlercodes sind typisiert.
Loslegen
Dein erster geteilter State in einer Minute.
Installieren, deklarieren, nutzen. Speichern fügst du hinzu, wenn deine App es braucht.
- 01Installieren
npm install @kkwaenggwari/restate - 02Einen Store deklarieren
export const useCart = restate({ items: 0, total: 0 }); - 03In jeder Komponente lesen
const items = useCart((cart) => cart.items);
KKWAENGGWARI