Estado para React e React Native · 1.0

Compartilhe estado.Um hook.Pronto.

Declare um store em uma linha e use em qualquer componente — sem Provider, actions ou reducers. Um componente só renderiza quando o valor que ele selecionou muda, e salvar acontece quando você decide.

Terminal
npm install @kkwaenggwari/restate

Ao vivo — um store real do restate

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

items e total mudaram — três cards renderizam, a mensagem fica parada.

01 — Fácil

O mesmo recurso, menos conceitos.

Quatro implementações da mesma tarefa, lado a lado. Os números vêm do código mostrado aqui e são medidos quando este site é compilado.

Contador compartilhado

Dois componentes leem e atualizam um mesmo número.

Redux Toolkit Organiza as atualizações em slices e actions despachadas para um store fornecido pelo contexto do React. DevTools excelentes e um fluxo de atualização rígido e previsível para equipes grandes.

Zustand Cria um hook de store com create() e o altera com setState. Minúsculo e flexível, com middleware para persistência e DevTools.

Jotai Compõe átomos e átomos derivados, opcionalmente dentro de um Provider. Dependências finas entre muitos pedaços pequenos de estado.

Redux Toolkit29 linhas · 5 APIs
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 linhas · 1 APIs
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 linhas · 2 APIs
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 Uma declaração é ao mesmo tempo o store e o hook.

restate13 linhas · 1 APIs
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 />
    </>
  );
}

Para esta tarefa, Zustand e Jotai também são pequenos e dispensam Provider. A diferença cresce quando entram o salvamento e o estado por documento.

Selecionar um campo

O componente do título não pode renderizar quando só o corpo muda.

Redux Toolkit Organiza as atualizações em slices e actions despachadas para um store fornecido pelo contexto do React. DevTools excelentes e um fluxo de atualização rígido e previsível para equipes grandes.

Zustand Cria um hook de store com create() e o altera com setState. Minúsculo e flexível, com middleware para persistência e DevTools.

Jotai Compõe átomos e átomos derivados, opcionalmente dentro de um Provider. Dependências finas entre muitos pedaços pequenos de estado.

Redux Toolkit26 linhas · 4 APIs
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 linhas · 1 APIs
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 linhas · 3 APIs
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 Passe um selector para o mesmo hook que você já usa.

restate17 linhas · 1 APIs
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>
    </>
  );
}

Todas as ferramentas aqui sabem selecionar. O restate usa a mesma chamada de hook em vez de um hook de selector separado ou um átomo derivado.

Salvar um rascunho

Restaurar um rascunho salvo na inicialização, salvar ao clicar e mostrar os estados salvando, não salvo e com falha.

Redux Toolkit Organiza as atualizações em slices e actions despachadas para um store fornecido pelo contexto do React. DevTools excelentes e um fluxo de atualização rígido e previsível para equipes grandes.

Zustand Cria um hook de store com create() e o altera com setState. Minúsculo e flexível, com middleware para persistência e DevTools.

Jotai Compõe átomos e átomos derivados, opcionalmente dentro de um Provider. Dependências finas entre muitos pedaços pequenos de estado.

Redux Toolkit84 linhas · 4 APIs
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 linhas · 1 APIs
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 linhas · 3 APIs
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 restaura automaticamente, e useStatus informa o salvamento, as alterações não salvas e os erros.

restate18 linhas · 1 APIs
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>
    </>
  );
}

As outras versões escrevem o mesmo código de IndexedDB à mão. O middleware de persistência ou os utilitários de armazenamento delas podem encurtar isso com um adaptador; esse código não é contado aqui.

Estado por documento

Dois documentos mantêm estado independente, e fechar um deles o libera.

Redux Toolkit Organiza as atualizações em slices e actions despachadas para um store fornecido pelo contexto do React. DevTools excelentes e um fluxo de atualização rígido e previsível para equipes grandes.

Zustand Cria um hook de store com create() e o altera com setState. Minúsculo e flexível, com middleware para persistência e DevTools.

Jotai Compõe átomos e átomos derivados, opcionalmente dentro de um Provider. Dependências finas entre muitos pedaços pequenos de estado.

Redux Toolkit42 linhas · 4 APIs
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 linhas · 2 APIs
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 linhas · 3 APIs
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 cria os stores e os destrói depois da desmontagem.

restate27 linhas · 2 APIs
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" />
    </>
  );
}

Um Provider ou store do Jotai e os vanilla stores do Zustand também isolam estado; você cria e conecta por conta própria.

Como esses números são contados

Linhas não vazias do código mostrado e nomes importados de cada biblioteca de estado (imports do React não contam). Todas as versões renderizam a mesma UI com o mesmo armazenamento e os mesmos estados de carregamento e erro. Os exemplos do restate supõem await restateManager.ready na entrada do app. As fontes foram conferidas com a documentação oficial de cada projeto:

  • Redux Toolkit — Redux Toolkit · Quick Start, React Redux · useSelector, Redux Toolkit · configureStore. Escolha quando um único log de actions auditável e um grande ecossistema de middleware forem o mais importante
  • Zustand — Zustand · Introduction, Zustand · persist. Escolha quando quiser o menor núcleo possível e preferir compor middleware
  • Jotai — Jotai · atom, Jotai · Storage, Jotai · Provider. Escolha quando seu estado for, por natureza, um grafo de pequenos valores derivados

02 — Rápido

Renderize só o que mudou. Grave uma vez só.

Os dois painéis rodam o pacote restate de verdade no seu navegador. Clique, digite e acompanhe os contadores.

Mapa de renders

Os mesmos quatro campos, lidos de duas formas. Mude um campo e conte os renders.

Lê o store inteiro
  • Nome
  • Tema
  • Contagem
  • Rascunho
Seleciona um campo
  • Nome
  • Tema
  • Contagem
  • Rascunho

Gravações agrupadas

O autoSave espera uma pausa na digitação e grava uma vez. Uma rajada de chamadas a save() compartilha uma única gravação na fila.

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

Os números vêm de renders reais do React e de um adaptador de armazenamento que conta as gravações nesta página. Outras bibliotecas também conseguem selecionar valores; aqui, selecionar é simplesmente o jeito de ler.

03 — Estável

Quando o armazenamento falha, seus dados ficam onde estão.

É ao salvar que o estado se perde: discos lentos, armazenamento bloqueado, abas fechadas. Estas são garantias do restate 1.0, não conselhos.

Abrir o laboratório de falhas Ler o guia de confiabilidade
  1. 01

    Os salvamentos rodam em ordem

    Cada store executa as operações de armazenamento uma depois da outra, então um salvamento lento nunca deixa um valor antigo sobrescrever um mais novo.

    save() · restore()
  2. 02

    Um salvamento na fila grava o valor mais recente

    Chamar save() enquanto outro salvamento espera na fila se junta a ele em vez de empilhar mais gravações.

    save(): Promise<T>
  3. 03

    Registros não lidos nunca são sobrescritos por acidente

    Se um registro não carregou, o autoSave e os salvamentos em lote não mexem nele e informam LOAD_FAILED. Só um save() explícito pode substituí-lo.

    LOAD_FAILED
  4. 04

    Mesmo desmontando, o salvamento termina

    Salvamentos pedidos antes da desmontagem são concluídos. Gravações posteriores em um store destruído são ignoradas, como o estado do React depois da desmontagem.

    useRestateScope
  5. 05

    Sem armazenamento? O app inicia mesmo assim

    Durante a renderização no servidor ou com o armazenamento bloqueado, os stores continuam funcionando em memória e informam que o armazenamento não está disponível.

    status().available
  6. 06

    Erros dizem o quê e onde

    Todo erro do restate traz um código tipado e a chave do registro, para você mostrar a mensagem certa.

    error.code · error.key

Cresce com o seu app

Mude a declaração. Mantenha seus componentes.

Salvar, salvar automaticamente e validar são opções do mesmo store. O componente da direita nunca muda.

  1. Memória

    Um valor compartilhado em memória.

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

    Restaurado automaticamente; gravado quando você chama save().

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

    Gravado uma vez depois de 500 ms sem digitar.

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

    Registros antigos são migrados e verificados antes do 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 — igual em todas as etapas

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

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

Roda onde o React roda

Um pacote para todo ambiente React.

  • react >=16.8.0

    React

    Funciona com React 16.8 ou superior, no navegador e na renderização no servidor.

  • react-native-mmkv >=2.0.0

    React Native

    Os mesmos imports. O salvamento usa MMKV assim que react-native-mmkv estiver instalado.

  • SSR

    Renderização no servidor

    Stores de componente pegam o snapshot do servidor das props e começam a carregar depois da hidratação.

  • index.d.ts

    TypeScript

    Declarações incluídas. Seletores, campos de status e códigos de erro são tipados.

Começar

Seu primeiro estado compartilhado em um minuto.

Instale, declare, use. Adicione o salvamento quando seu app precisar.

  1. 01
    Instalar
    Terminal
    npm install @kkwaenggwari/restate
  2. 02
    Declarar um store
    JavaScript / JSX
    export const useCart = restate({ items: 0, total: 0 });
  3. 03
    Ler em qualquer componente
    JavaScript / JSX
    const items = useCart((cart) => cart.items);

KKWAENGGWARI

Projetos

Pesquisar na documentação

Pesquisa em títulos, descrições e texto de todos os guias.