State voor React en React Native · 1.0
Deel state.Eén hook.Klaar.
Declareer een store in één regel en gebruik hem in elk component, zonder Provider, actions of reducers. Een component rendert alleen opnieuw als de waarde die het selecteerde verandert, en opslaan gebeurt wanneer jij besluit.
npm install @kkwaenggwari/restateLive — een echte restate-store
useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))items en total veranderen: drie kaarten renderen, de cadeaukaart blijft stil.
- Makkelijk0Provider, actions en reducers om in te richtenDeclareer een waarde en gebruik hem. Er hoeft vooraf niets te worden gekoppeld.
- Snel11 → 1Elf bewerkingen, één schrijfactieautoSave wacht op een pauze, dus elf snelle bewerkingen worden één schrijfactie.
- Stabiel26Getypeerde foutcodesElke fout vertelt wat er misging en welk record het raakte.
01 — Makkelijk
Dezelfde functionaliteit, minder begrippen.
Vier implementaties van dezelfde taak naast elkaar. De getallen komen uit de getoonde code en worden gemeten tijdens de build van deze site.
Gedeelde teller
Twee componenten lezen en wijzigen hetzelfde getal.
Redux Toolkit Organiseert updates in slices en actions die worden verstuurd naar een store die via React-context wordt geleverd. Uitstekende DevTools en een strikte, voorspelbare updatestroom voor grote teams.
Zustand Maakt een store-hook met create() en wijzigt die met setState. Piepklein en flexibel, met middleware voor opslag en DevTools.
Jotai Stelt atoms en afgeleide atoms samen, optioneel binnen een Provider. Fijnmazige afhankelijkheden tussen veel kleine stukjes state.
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 Eén declaratie is zowel de store als de 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 />
</>
);
}Voor deze taak zijn Zustand en Jotai ook klein en zonder Provider. Het verschil groeit zodra opslaan en state per document erbij komen.
Selecteer een veld
Het titelcomponent moet niet renderen als alleen de tekst verandert.
Redux Toolkit Organiseert updates in slices en actions die worden verstuurd naar een store die via React-context wordt geleverd. Uitstekende DevTools en een strikte, voorspelbare updatestroom voor grote teams.
Zustand Maakt een store-hook met create() en wijzigt die met setState. Piepklein en flexibel, met middleware voor opslag en DevTools.
Jotai Stelt atoms en afgeleide atoms samen, optioneel binnen een Provider. Fijnmazige afhankelijkheden tussen veel kleine stukjes state.
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 Geef een selector door aan dezelfde hook die je al gebruikt.
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>
</>
);
}Alle tools hier kunnen selecteren. restate gebruikt dezelfde hook-aanroep in plaats van een aparte selector-hook of een afgeleid atom.
Een concept opslaan
Zet bij het opstarten een opgeslagen concept terug, sla op bij een klik en toon de statussen opslaan, niet opgeslagen en mislukt.
Redux Toolkit Organiseert updates in slices en actions die worden verstuurd naar een store die via React-context wordt geleverd. Uitstekende DevTools en een strikte, voorspelbare updatestroom voor grote teams.
Zustand Maakt een store-hook met create() en wijzigt die met setState. Piepklein en flexibel, met middleware voor opslag en DevTools.
Jotai Stelt atoms en afgeleide atoms samen, optioneel binnen een Provider. Fijnmazige afhankelijkheden tussen veel kleine stukjes state.
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 zet automatisch terug, en useStatus meldt opslaan, niet-opgeslagen bewerkingen en fouten.
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>
</>
);
}De andere versies schrijven dezelfde IndexedDB-code met de hand. Hun opslag-middleware of opslaghulpmiddelen kunnen die met een adapter inkorten; die code wordt hier niet meegeteld.
State per document
Twee documenten houden onafhankelijke state, en een ervan sluiten geeft die vrij.
Redux Toolkit Organiseert updates in slices en actions die worden verstuurd naar een store die via React-context wordt geleverd. Uitstekende DevTools en een strikte, voorspelbare updatestroom voor grote teams.
Zustand Maakt een store-hook met create() en wijzigt die met setState. Piepklein en flexibel, met middleware voor opslag en DevTools.
Jotai Stelt atoms en afgeleide atoms samen, optioneel binnen een Provider. Fijnmazige afhankelijkheden tussen veel kleine stukjes state.
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 maakt stores aan en vernietigt ze na de 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" />
</>
);
}Een Jotai-Provider of -store en vanilla stores van Zustand kunnen state ook isoleren; aanmaken en koppelen doe je zelf.
Hoe deze getallen worden geteld
Niet-lege regels van de getoonde code en namen die uit elke state-library worden geïmporteerd (React-imports tellen niet mee). Elke versie rendert dezelfde UI met dezelfde opslag en dezelfde laad- en foutstatussen. De restate-voorbeelden gaan uit van await restateManager.ready in het toegangspunt van de app. Bronnen gecontroleerd aan de hand van de officiële documentatie van elk project:
- Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Kies het als één controleerbaar actionlog en een groot middleware-ecosysteem het belangrijkst zijn
- Zustand — Zustand · Introduction, Zustand · persist. Kies het als je de kleinste kern wilt en liever middleware samenstelt
- Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Kies het als je state van nature een graaf van kleine afgeleide waarden is
02 — Snel
Render alleen wat veranderde. Schrijf één keer.
Beide panelen draaien het echte restate-pakket in je browser. Klik, typ en kijk naar de tellers.
Renderkaart
Dezelfde vier velden, op twee manieren gelezen. Verander een veld en tel de renders.
- Naam
- Thema
- Teller
- Concept
- Naam
- Thema
- Teller
- Concept
Samengevoegde schrijfacties
autoSave wacht op een pauze in het typen en schrijft dan één keer. Een reeks save()-aanroepen deelt één schrijfactie in de wachtrij.
persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });De getallen komen uit echte React-renders en een opslagadapter die op deze pagina schrijfacties telt. Andere libraries kunnen ook waarden selecteren; hier is selecteren gewoon de manier van lezen.
03 — Stabiel
Als de opslag faalt, blijven je gegevens waar ze zijn.
Bij het opslaan raakt state kwijt: trage schijven, geblokkeerde opslag, gesloten tabbladen. Dit zijn garanties van restate 1.0, geen adviezen.
- 01
Opslaan gebeurt op volgorde
Elke store voert opslagbewerkingen na elkaar uit, dus een trage save laat nooit een oude waarde een nieuwere overschrijven.
save() · restore() - 02
Een wachtende save neemt de nieuwste waarde
Een save() aanroepen terwijl een andere in de wachtrij staat, sluit daarbij aan in plaats van extra schrijfacties op te stapelen.
save(): Promise<T> - 03
Ongelezen records worden nooit per ongeluk overschreven
Als een record niet laadde, laten autoSave en bulksaves het ongemoeid en melden ze LOAD_FAILED. Alleen een expliciete save() kan het vervangen.
LOAD_FAILED - 04
Ook na unmount wordt de save afgemaakt
Saves die vóór de unmount zijn aangevraagd, worden afgemaakt. Latere schrijfacties naar een vernietigde store worden genegeerd, net als React-state na unmount.
useRestateScope - 05
Geen opslag? De app start toch
Tijdens server-rendering of met geblokkeerde opslag blijven stores in het geheugen werken en melden ze dat opslag niet beschikbaar is.
status().available - 06
Fouten vertellen wat en waar
Elke restate-fout heeft een getypeerde code en de sleutel van het record, zodat je de juiste melding kunt tonen.
error.code · error.key
Groeit mee met je app
Verander de declaratie. Houd je componenten.
Opslaan, automatisch opslaan en validatie zijn opties van dezelfde store. Het component rechts verandert nooit.
Geheugen
Een gedeelde waarde in het geheugen.
import { restate } from '@kkwaenggwari/restate'; export const useDraft = restate('');Opgeslagen
Automatisch teruggezet; geschreven als je save() aanroept.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft' });Automatisch opslaan
Eén keer geschreven na een typpauze van 500 ms.
import { persistedRestate } from '@kkwaenggwari/restate'; export const useDraft = persistedRestate('', { key: 'draft', autoSave: { delayMs: 500 }, });Gevalideerd
Oudere records worden gemigreerd en gecontroleerd vóór gebruik.
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', });
Component — bij elke stap hetzelfde
function Editor() {
const text = useDraft();
return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}Werkt waar React werkt
Eén pakket voor elke React-omgeving.
react >=16.8.0React
Werkt met React 16.8 en nieuwer, in de browser en tijdens server-rendering.
react-native-mmkv >=2.0.0React Native
Dezelfde imports. Opslaan gebruikt MMKV zodra je react-native-mmkv installeert.
SSRServer-rendering
Stores van een component nemen de server-snapshot uit props en beginnen na hydration met laden.
index.d.tsTypeScript
Declaraties inbegrepen. Selectors, statusvelden en foutcodes zijn getypeerd.
Aan de slag
Je eerste gedeelde state in een minuut.
Installeer, declareer, gebruik. Voeg opslaan toe zodra je app het nodig heeft.
- 01Installeren
npm install @kkwaenggwari/restate - 02Declareer een store
export const useCart = restate({ items: 0, total: 0 }); - 03Lees hem in elk component
const items = useCart((cart) => cart.items);
KKWAENGGWARI