State til React og React Native · 1.0

Del state.Én hook.Færdig.

Erklær en store på én linje, og brug den i enhver komponent uden Provider, actions eller reducers. En komponent renderes kun igen, når den værdi, den har valgt, ændrer sig, og gem sker, når du beslutter det.

Terminal
npm install @kkwaenggwari/restate

Live — en rigtig restate-store

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

items og total ændrer sig: tre kort renderes, gavekortet står stille.

01 — Nemt

Samme funktion, færre begreber.

Fire implementeringer af samme opgave side om side. Tallene kommer fra koden, der vises her, og måles, når sitet bygges.

Delt tæller

To komponenter læser og opdaterer det samme tal.

Redux Toolkit Organiserer opdateringer i slices og actions, der sendes til en store, som leveres via React-kontekst. Fremragende DevTools og et stramt, forudsigeligt opdateringsflow til store teams.

Zustand Opretter en store-hook med create() og ændrer den med setState. Lillebitte og fleksibel, med middleware til persistens og DevTools.

Jotai Sætter atoms og afledte atoms sammen, eventuelt inde i en Provider. Finkornede afhængigheder mellem mange små stykker state.

Redux Toolkit29 linjer · 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 linjer · 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 linjer · 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 Én erklæring er både storen og hooken.

restate13 linjer · 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 />
    </>
  );
}

Til denne opgave er Zustand og Jotai også små og uden Provider. Forskellen vokser, når gem og state pr. dokument kommer med.

Vælg et felt

Titelkomponenten skal ikke renderes, når kun teksten ændrer sig.

Redux Toolkit Organiserer opdateringer i slices og actions, der sendes til en store, som leveres via React-kontekst. Fremragende DevTools og et stramt, forudsigeligt opdateringsflow til store teams.

Zustand Opretter en store-hook med create() og ændrer den med setState. Lillebitte og fleksibel, med middleware til persistens og DevTools.

Jotai Sætter atoms og afledte atoms sammen, eventuelt inde i en Provider. Finkornede afhængigheder mellem mange små stykker state.

Redux Toolkit26 linjer · 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 linjer · 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 linjer · 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 Giv en selector til den samme hook, du allerede bruger.

restate17 linjer · 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>
    </>
  );
}

Alle værktøjer her kan vælge. restate bruger det samme hook-kald i stedet for en separat selector-hook eller et afledt atom.

Gem en kladde

Gendan en gemt kladde ved opstart, gem ved klik, og vis tilstandene gemmer, ikke gemt og mislykkedes.

Redux Toolkit Organiserer opdateringer i slices og actions, der sendes til en store, som leveres via React-kontekst. Fremragende DevTools og et stramt, forudsigeligt opdateringsflow til store teams.

Zustand Opretter en store-hook med create() og ændrer den med setState. Lillebitte og fleksibel, med middleware til persistens og DevTools.

Jotai Sætter atoms og afledte atoms sammen, eventuelt inde i en Provider. Finkornede afhængigheder mellem mange små stykker state.

Redux Toolkit84 linjer · 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 linjer · 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 linjer · 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 gendanner automatisk, og useStatus melder om gem, ikke-gemte ændringer og fejl.

restate18 linjer · 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 andre versioner skriver den samme IndexedDB-kode i hånden. Deres persistens-middleware eller lagerværktøjer kan forkorte den med en adapter; den kode tælles ikke med her.

State pr. dokument

To dokumenter har uafhængig state, og at lukke det ene frigiver den.

Redux Toolkit Organiserer opdateringer i slices og actions, der sendes til en store, som leveres via React-kontekst. Fremragende DevTools og et stramt, forudsigeligt opdateringsflow til store teams.

Zustand Opretter en store-hook med create() og ændrer den med setState. Lillebitte og fleksibel, med middleware til persistens og DevTools.

Jotai Sætter atoms og afledte atoms sammen, eventuelt inde i en Provider. Finkornede afhængigheder mellem mange små stykker state.

Redux Toolkit42 linjer · 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 linjer · 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 linjer · 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 opretter stores og nedlægger dem efter unmount.

restate27 linjer · 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 og vanilla stores i Zustand kan også isolere state; du opretter og forbinder dem selv.

Sådan tælles tallene

Ikke-tomme linjer i den viste kode og navne, der importeres fra hvert state-bibliotek (React-imports tælles ikke). Hver version renderer samme UI med samme lager og samme indlæsnings- og fejltilstande. restate-eksemplerne forudsætter await restateManager.ready i appens indgangspunkt. Kilderne er tjekket mod hvert projekts officielle dokumentation:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Vælg det, når én revisionsvenlig action-log og et stort middleware-økosystem betyder mest
  • Zustand — Zustand · Introduction, Zustand · persist. Vælg det, når du vil have den mindst mulige kerne og hellere sammensætter middleware
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Vælg det, når din state af natur er en graf af små afledte værdier

02 — Hurtigt

Render kun det, der ændrede sig. Skriv én gang.

Begge paneler kører den rigtige restate-pakke i din browser. Klik, skriv, og hold øje med tællerne.

Renderingskort

De samme fire felter læst på to måder. Ændr et felt, og tæl renderingerne.

Læser hele storen
  • Navn
  • Tema
  • Tæller
  • Kladde
Vælger ét felt
  • Navn
  • Tema
  • Tæller
  • Kladde

Samlede skrivninger

autoSave venter på en pause i skrivningen og skriver derefter én gang. En række save()-kald deler én skrivning i køen.

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

Tallene kommer fra rigtige React-renderinger og en lageradapter, der tæller skrivninger på denne side. Andre biblioteker kan også vælge værdier; her er udvalg simpelthen måden at læse på.

03 — Stabilt

Når lageret svigter, bliver dine data, hvor de er.

Det er ved gem, state går tabt: langsomme diske, blokeret lager, lukkede faner. Dette er garantier i restate 1.0, ikke råd.

Åbn fejllaboratoriet Læs guiden om pålidelighed
  1. 01

    Gem sker i rækkefølge

    Hver store kører lagerhandlinger én ad gangen, så et langsomt gem aldrig lader en gammel værdi overskrive en nyere.

    save() · restore()
  2. 02

    Et ventende gem tager den nyeste værdi

    At kalde save(), mens et andet gem venter i køen, slutter sig til det i stedet for at stable flere skrivninger.

    save(): Promise<T>
  3. 03

    Ulæste poster overskrives aldrig ved et uheld

    Hvis en post ikke blev indlæst, lader autoSave og masse-gem den være og melder LOAD_FAILED. Kun et eksplicit save() kan erstatte den.

    LOAD_FAILED
  4. 04

    Selv efter unmount gøres gemmet færdigt

    Gem, der blev bedt om før unmount, gøres færdige. Senere skrivninger til en nedlagt store ignoreres, ligesom React-state efter unmount.

    useRestateScope
  5. 05

    Intet lager? Appen starter alligevel

    Under serverrendering eller med blokeret lager fortsætter stores i hukommelsen og melder, at lageret ikke er tilgængeligt.

    status().available
  6. 06

    Fejl fortæller hvad og hvor

    Hver restate-fejl har en typet kode og sin posts nøgle, så du kan vise den rigtige besked.

    error.code · error.key

Vokser med din app

Ændr erklæringen. Behold dine komponenter.

Gem, automatisk gem og validering er indstillinger på den samme store. Komponenten til højre ændrer sig aldrig.

  1. Hukommelse

    En delt værdi i hukommelsen.

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

    Gendannes automatisk; skrives, når du kalder save().

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

    Skrives én gang efter 500 ms pause i skrivningen.

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

    Ældre poster migreres og kontrolleres før brug.

    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 — den samme ved hvert trin

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

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

Virker, hvor React virker

Én pakke til alle React-miljøer.

  • react >=16.8.0

    React

    Virker med React 16.8 og nyere, i browseren og under serverrendering.

  • react-native-mmkv >=2.0.0

    React Native

    Samme imports. Gem bruger MMKV, så snart du installerer react-native-mmkv.

  • SSR

    Serverrendering

    En komponents stores tager serverens snapshot fra props og begynder at indlæse efter hydrering.

  • index.d.ts

    TypeScript

    Deklarationer medfølger. Selectorer, statusfelter og fejlkoder er typede.

Kom i gang

Din første delte state på et minut.

Installér, erklær, brug. Tilføj gem, når din app har brug for det.

  1. 01
    Installér
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Erklær en store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Læs den i enhver komponent
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Projekter

Søg i dokumentationen

Søg i titler, beskrivelser og tekst i alle guides.