Stato per React e React Native · 1.0

Condividi lo stato.Un hook.Fatto.

Dichiara uno store in una riga e usalo in qualsiasi componente, senza Provider, action o reducer. Un componente si renderizza solo quando cambia il valore che ha selezionato, e il salvataggio avviene quando decidi tu.

Terminal
npm install @kkwaenggwari/restate

Dal vivo — un vero store restate

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

items e total cambiano: tre card si renderizzano, il biglietto resta fermo.

01 — Facile

La stessa funzionalità, meno concetti.

Quattro implementazioni dello stesso compito, una accanto all’altra. I numeri vengono dal codice mostrato qui e sono misurati durante la build di questo sito.

Contatore condiviso

Due componenti leggono e aggiornano lo stesso numero.

Redux Toolkit Organizza gli aggiornamenti in slice e action inviate a uno store fornito tramite il context di React. DevTools eccellenti e un flusso di aggiornamento rigoroso e prevedibile per team grandi.

Zustand Crea un hook dello store con create() e lo modifica con setState. Minuscolo e flessibile, con middleware per la persistenza e i DevTools.

Jotai Compone atom e atom derivati, facoltativamente dentro un Provider. Dipendenze a grana fine tra molti piccoli pezzi di stato.

Redux Toolkit29 righe · 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 righe · 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 righe · 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 Una sola dichiarazione è sia lo store sia l’hook.

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

Per questo compito anche Zustand e Jotai sono piccoli e senza Provider. Il divario cresce quando entrano in gioco il salvataggio e lo stato per documento.

Seleziona un campo

Il componente del titolo non deve renderizzarsi quando cambia solo il testo.

Redux Toolkit Organizza gli aggiornamenti in slice e action inviate a uno store fornito tramite il context di React. DevTools eccellenti e un flusso di aggiornamento rigoroso e prevedibile per team grandi.

Zustand Crea un hook dello store con create() e lo modifica con setState. Minuscolo e flessibile, con middleware per la persistenza e i DevTools.

Jotai Compone atom e atom derivati, facoltativamente dentro un Provider. Dipendenze a grana fine tra molti piccoli pezzi di stato.

Redux Toolkit26 righe · 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 righe · 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 righe · 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 Passa un selector allo stesso hook che usi già.

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

Tutti gli strumenti qui sanno selezionare. restate usa la stessa chiamata all’hook invece di un hook di selector separato o di un atom derivato.

Salva una bozza

Ripristina all’avvio una bozza salvata, salva al clic e mostra gli stati di salvataggio in corso, non salvato e fallito.

Redux Toolkit Organizza gli aggiornamenti in slice e action inviate a uno store fornito tramite il context di React. DevTools eccellenti e un flusso di aggiornamento rigoroso e prevedibile per team grandi.

Zustand Crea un hook dello store con create() e lo modifica con setState. Minuscolo e flessibile, con middleware per la persistenza e i DevTools.

Jotai Compone atom e atom derivati, facoltativamente dentro un Provider. Dipendenze a grana fine tra molti piccoli pezzi di stato.

Redux Toolkit84 righe · 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 righe · 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 righe · 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 ripristina in automatico, e useStatus segnala il salvataggio, le modifiche non salvate e gli errori.

restate18 righe · 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>
    </>
  );
}

Le altre versioni scrivono a mano lo stesso codice IndexedDB. Il loro middleware di persistenza o le loro utility di archiviazione possono accorciarlo con un adattatore; quel codice qui non è contato.

Stato per documento

Due documenti mantengono uno stato indipendente, e chiuderne uno lo rilascia.

Redux Toolkit Organizza gli aggiornamenti in slice e action inviate a uno store fornito tramite il context di React. DevTools eccellenti e un flusso di aggiornamento rigoroso e prevedibile per team grandi.

Zustand Crea un hook dello store con create() e lo modifica con setState. Minuscolo e flessibile, con middleware per la persistenza e i DevTools.

Jotai Compone atom e atom derivati, facoltativamente dentro un Provider. Dipendenze a grana fine tra molti piccoli pezzi di stato.

Redux Toolkit42 righe · 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 righe · 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 righe · 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 crea gli store e li distrugge dopo lo smontaggio.

restate27 righe · 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" />
    </>
  );
}

Anche un Provider o uno store di Jotai e i vanilla store di Zustand possono isolare lo stato; li crei e li colleghi tu.

Come vengono contati questi numeri

Righe non vuote del codice mostrato e nomi importati da ogni libreria di stato (gli import di React non contano). Ogni versione renderizza la stessa UI con lo stesso archivio e gli stessi stati di caricamento ed errore. Gli esempi restate presuppongono await restateManager.ready nell’entry point dell’app. Le fonti sono state verificate sulla documentazione ufficiale di ogni progetto:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Sceglilo quando contano soprattutto un unico log di action verificabile e un grande ecosistema di middleware
  • Zustand — Zustand · Introduction, Zustand · persist. Sceglilo quando vuoi il nucleo più piccolo possibile e preferisci comporre middleware
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Sceglilo quando il tuo stato è per natura un grafo di piccoli valori derivati

02 — Veloce

Renderizza solo ciò che è cambiato. Scrivi una volta sola.

Entrambi i pannelli eseguono il vero pacchetto restate nel tuo browser. Clicca, scrivi e guarda i contatori.

Mappa dei render

Gli stessi quattro campi letti in due modi. Cambia un campo e conta i render.

Legge tutto lo store
  • Nome
  • Tema
  • Contatore
  • Bozza
Seleziona un campo
  • Nome
  • Tema
  • Contatore
  • Bozza

Scritture unite

autoSave aspetta una pausa nella digitazione e poi scrive una volta. Una raffica di chiamate a save() condivide una sola scrittura in coda.

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

I numeri vengono da veri render di React e da un adattatore di archiviazione che conta le scritture in questa pagina. Anche altre librerie sanno selezionare valori; qui selezionare è semplicemente il modo di leggere.

03 — Stabile

Quando l’archivio fallisce, i tuoi dati restano dove sono.

È durante il salvataggio che lo stato si perde: dischi lenti, archivio bloccato, tab chiuse. Queste sono garanzie di restate 1.0, non consigli.

Apri il laboratorio dei guasti Leggi la guida all’affidabilità
  1. 01

    I salvataggi avvengono in ordine

    Ogni store esegue le operazioni di archiviazione una dopo l’altra, quindi un salvataggio lento non lascia mai che un valore vecchio sovrascriva uno più recente.

    save() · restore()
  2. 02

    Un salvataggio in attesa prende il valore più recente

    Chiamare save() mentre un altro salvataggio aspetta in coda si unisce a quello invece di accumulare altre scritture.

    save(): Promise<T>
  3. 03

    I record non letti non vengono mai sovrascritti per sbaglio

    Se un record non si è caricato, autoSave e i salvataggi in blocco non lo toccano e segnalano LOAD_FAILED. Solo un save() esplicito può sostituirlo.

    LOAD_FAILED
  4. 04

    Anche dopo lo smontaggio, il salvataggio finisce

    I salvataggi richiesti prima dello smontaggio vengono completati. Le scritture successive in uno store distrutto vengono ignorate, come lo stato di React dopo lo smontaggio.

    useRestateScope
  5. 05

    Niente archivio? L’app parte lo stesso

    Durante il rendering sul server o con l’archivio bloccato, gli store continuano a funzionare in memoria e segnalano che l’archivio non è disponibile.

    status().available
  6. 06

    Gli errori dicono che cosa e dove

    Ogni errore di restate porta un codice tipizzato e la chiave del suo record, così puoi mostrare il messaggio giusto.

    error.code · error.key

Cresce con la tua app

Cambia la dichiarazione. Tieni i tuoi componenti.

Salvataggio, salvataggio automatico e validazione sono opzioni dello stesso store. Il componente a destra non cambia mai.

  1. Memoria

    Un valore condiviso in memoria.

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

    Ripristinato in automatico; scritto quando chiami save().

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

    Scritto una volta dopo 500 ms di pausa nella digitazione.

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

    I record più vecchi vengono migrati e controllati prima dell’uso.

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

Componente — uguale a ogni passo

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

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

Funziona dove funziona React

Un pacchetto per ogni ambiente React.

  • react >=16.8.0

    React

    Funziona con React 16.8 e successivi, nel browser e durante il rendering sul server.

  • react-native-mmkv >=2.0.0

    React Native

    Gli stessi import. Il salvataggio usa MMKV non appena installi react-native-mmkv.

  • SSR

    Rendering sul server

    Gli store di un componente prendono lo snapshot del server dalle props e iniziano a caricare dopo l’idratazione.

  • index.d.ts

    TypeScript

    Dichiarazioni incluse. Selettori, campi di stato e codici di errore sono tipizzati.

Inizia

Il tuo primo stato condiviso in un minuto.

Installa, dichiara, usa. Aggiungi il salvataggio quando la tua app ne ha bisogno.

  1. 01
    Installa
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Dichiara uno store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Leggilo in qualsiasi componente
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Progetti

Cerca nella documentazione

Cerca nei titoli, nelle descrizioni e nel testo di tutte le guide.