State för React och React Native · 1.0

Dela state.En hook.Klart.

Deklarera en store på en rad och använd den i vilken komponent som helst, utan Provider, actions eller reducers. En komponent renderas om bara när värdet den valt ändras, och sparande sker när du bestämmer.

Terminal
npm install @kkwaenggwari/restate

Live — en riktig restate-store

useCart.set(cart => ({ ...cart, items: cart.items + 1, total: cart.total + 12 }))

items och total ändras: tre kort renderas, presentkortet står still.

01 — Enkelt

Samma funktion, färre begrepp.

Fyra implementationer av samma uppgift sida vid sida. Siffrorna kommer från koden som visas här och mäts när webbplatsen byggs.

Delad räknare

Två komponenter läser och uppdaterar samma tal.

Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.

Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.

Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.

Redux Toolkit29 rader · 5 API
JavaScript / JSX
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>
  );
}
Zustand16 rader · 1 API
JavaScript / JSX
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 />
    </>
  );
}
Jotai13 rader · 2 API
JavaScript / JSX
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 En deklaration är både storen och hooken.

restate13 rader · 1 API
JavaScript / JSX
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 den här uppgiften är Zustand och Jotai också små och klarar sig utan Provider. Skillnaden växer när sparande och state per dokument kommer in.

Välj ett fält

Rubrikkomponenten ska inte renderas när bara texten ändras.

Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.

Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.

Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.

Redux Toolkit26 rader · 4 API
JavaScript / JSX
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>
  );
}
Zustand17 rader · 1 API
JavaScript / JSX
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>
    </>
  );
}
Jotai19 rader · 3 API
JavaScript / JSX
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 Skicka en selector till samma hook som du redan använder.

restate17 rader · 1 API
JavaScript / JSX
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>
    </>
  );
}

Alla verktyg här kan välja. restate använder samma hook-anrop i stället för en separat selector-hook eller en härledd atom.

Spara ett utkast

Återställ ett sparat utkast vid start, spara vid klick och visa tillstånden sparar, osparat och misslyckat.

Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.

Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.

Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.

Redux Toolkit84 rader · 4 API
JavaScript / JSX
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>
  );
}
Zustand79 rader · 1 API
JavaScript / JSX
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 />;
}
Jotai80 rader · 3 API
JavaScript / JSX
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 återställer automatiskt, och useStatus rapporterar sparande, osparade ändringar och fel.

restate18 rader · 1 API
JavaScript / JSX
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 andra versionerna skriver samma IndexedDB-kod för hand. Deras persistens-middleware eller lagringsverktyg kan korta den med en adapter; den koden räknas inte här.

State per dokument

Två dokument har oberoende state, och att stänga det ena släpper det.

Redux Toolkit Organiserar uppdateringar i slices och actions som skickas till en store som tillhandahålls via React-kontext. Utmärkta DevTools och ett strikt, förutsägbart uppdateringsflöde för stora team.

Zustand Skapar en store-hook med create() och ändrar den med setState. Pytteliten och flexibel, med middleware för persistens och DevTools.

Jotai Sätter ihop atoms och härledda atoms, valfritt inuti en Provider. Finkorniga beroenden mellan många små bitar state.

Redux Toolkit42 rader · 4 API
JavaScript / JSX
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" />
    </>
  );
}
Zustand35 rader · 2 API
JavaScript / JSX
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" />
    </>
  );
}
Jotai33 rader · 3 API
JavaScript / JSX
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 skapar stores och förstör dem efter unmount.

restate27 rader · 2 API
JavaScript / JSX
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" />
    </>
  );
}

En Provider eller store i Jotai och vanilla-stores i Zustand kan också isolera state; du skapar och kopplar dem själv.

Så räknas siffrorna

Icke-tomma rader i koden som visas och namn som importeras från varje state-bibliotek (React-importer räknas inte). Varje version renderar samma UI med samma lagring och samma laddnings- och feltillstånd. restate-exemplen förutsätter await restateManager.ready i appens startpunkt. Källorna har kontrollerats mot varje projekts officiella dokumentation:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Välj det när en enda granskningsbar actionlogg och ett stort middleware-ekosystem väger tyngst
  • Zustand — Zustand · Introduction, Zustand · persist. Välj det när du vill ha minsta möjliga kärna och hellre bygger med middleware
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Välj det när ditt state till sin natur är en graf av små härledda värden

02 — Snabbt

Rendera bara det som ändrats. Skriv en gång.

Båda panelerna kör det riktiga restate-paketet i din webbläsare. Klicka, skriv och titta på räknarna.

Renderingskarta

Samma fyra fält lästa på två sätt. Ändra ett fält och räkna renderingarna.

Läser hela storen
  • Namn
  • Tema
  • Räknare
  • Utkast
Väljer ett fält
  • Namn
  • Tema
  • Räknare
  • Utkast

Sammanslagna skrivningar

autoSave väntar på en paus i skrivandet och skriver sedan en gång. En serie save()-anrop delar en skrivning i kön.

persistedRestate('', { key: 'note', autoSave: { delayMs: 400 } });

Siffrorna kommer från riktiga React-renderingar och en lagringsadapter som räknar skrivningar på den här sidan. Andra bibliotek kan också välja värden; här är urval helt enkelt sättet att läsa.

03 — Stabilt

När lagringen fallerar ligger din data kvar.

Det är vid sparandet som state går förlorat: långsamma diskar, blockerad lagring, stängda flikar. Det här är garantier i restate 1.0, inte råd.

Öppna fellabbet Läs guiden om tillförlitlighet
  1. 01

    Sparningar sker i ordning

    Varje store kör lagringsåtgärder en i taget, så en långsam sparning låter aldrig ett gammalt värde skriva över ett nyare.

    save() · restore()
  2. 02

    En väntande sparning tar det senaste värdet

    Att anropa save() medan en annan sparning väntar i kön ansluter till den i stället för att stapla fler skrivningar.

    save(): Promise<T>
  3. 03

    Olästa poster skrivs aldrig över av misstag

    Om en post inte lästes in lämnar autoSave och massparningar den orörd och rapporterar LOAD_FAILED. Bara ett explicit save() kan ersätta den.

    LOAD_FAILED
  4. 04

    Även efter unmount slutförs sparningen

    Sparningar som begärts före unmount slutförs. Senare skrivningar till en förstörd store ignoreras, precis som React-state efter unmount.

    useRestateScope
  5. 05

    Ingen lagring? Appen startar ändå

    Under serverrendering eller med blockerad lagring fortsätter stores att fungera i minnet och rapporterar att lagring inte är tillgänglig.

    status().available
  6. 06

    Fel berättar vad och var

    Varje restate-fel har en typad kod och sin posts nyckel, så att du kan visa rätt meddelande.

    error.code · error.key

Växer med din app

Ändra deklarationen. Behåll dina komponenter.

Sparande, autosparande och validering är alternativ på samma store. Komponenten till höger ändras aldrig.

  1. Minne

    Ett delat värde i minnet.

    import { restate } from '@kkwaenggwari/restate';
    
    export const useDraft = restate('');
  2. Sparad

    Återställs automatiskt; skrivs när du anropar save().

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', { key: 'draft' });
  3. Autosparande

    Skrivs en gång efter 500 ms paus i skrivandet.

    import { persistedRestate } from '@kkwaenggwari/restate';
    
    export const useDraft = persistedRestate('', {
      key: 'draft',
      autoSave: { delayMs: 500 },
    });
  4. Validerad

    Äldre poster migreras och kontrolleras före användning.

    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',
    });

Komponent — samma i varje steg

function Editor() {
  const text = useDraft();

  return <textarea value={text} onChange={(event) => useDraft.set(event.target.value)} />;
}

Fungerar där React fungerar

Ett paket för varje React-miljö.

  • react >=16.8.0

    React

    Fungerar med React 16.8 och senare, i webbläsaren och vid serverrendering.

  • react-native-mmkv >=2.0.0

    React Native

    Samma importer. Sparande använder MMKV så fort du installerar react-native-mmkv.

  • SSR

    Serverrendering

    En komponents stores tar serverns ögonblicksbild från props och börjar läsa in efter hydrering.

  • index.d.ts

    TypeScript

    Deklarationer ingår. Selektorer, statusfält och felkoder är typade.

Kom igång

Ditt första delade state på en minut.

Installera, deklarera, använd. Lägg till sparande när appen behöver det.

  1. 01
    Installera
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Deklarera en store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Läs den i vilken komponent som helst
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Projekt

Sök i dokumentationen

Sök i rubriker, beskrivningar och text i alla guider.