restate / v1.0.0

State owned by a component

Give each document, dialog or request its own stores that are created with the component and released after it unmounts.

On this pageuseRestateScopeRules of the factoryInitial values from propsServer renderingWait for a document’s saved dataTry itOwners outside React

useRestateScope

The factory declares stores and returns them. restate keeps the result across renders and destroys the scope after the component unmounts. There is no close function or cleanup effect to write.

Two Document elements have independent state. To share a document with its children, pass the returned hooks through props or your own context.

JavaScript / JSX
import { restate, useRestateScope } from '@kkwaenggwari/restate';

function Document() {
  const { useDraft, useSelection } = useRestateScope((scope) => ({
    useDraft: restate('', { scope }),
    useSelection: restate(0, { scope }),
  }));
  const text = useDraft();
  const selection = useSelection();

  return (
    <section>
      <input value={text} onChange={(event) => useDraft.set(event.target.value)} />
      <button onClick={() => useSelection.set((n) => n + 1)}>Selection: {selection}</button>
    </section>
  );
}

Rules of the factory

React may call the factory more than once and discard results, so keep it synchronous and pure.

  • Declare stores in the factory; call the returned hooks at the top level of the component.
  • Initial props are captured once, like a useState initializer.
  • Give the component a key when a different document needs fresh state.
  • Without a factory, useRestateScope() returns a stable scope for stores you create later in effects or handlers.

Initial values from props

Use props as initial values and a React key to start over for another document.

JavaScript / JSX
function Editor({ document }) {
  // The initial value is captured once, like a useState initializer.
  const useTitle = useRestateScope((scope) => restate(document.title, { scope }));
  const title = useTitle();
  return <input value={title} onChange={(event) => useTitle.set(event.target.value)} />;
}

// A different document gets fresh state.
<Editor key={document.id} document={document} />;

Server rendering

Load the data in your server route and pass it as a prop. The initial value also supplies the server snapshot. In the Next.js App Router, this hook belongs in a Client Component, which is still server rendered.

JavaScript / JSX
'use client';

import { restate, useRestateScope } from '@kkwaenggwari/restate';

export default function Profile({ user }) {
  const useProfile = useRestateScope((scope) => restate(user, { scope }));
  const profile = useProfile();
  return <p>{profile.name}</p>;
}

Wait for a document’s saved data

Saved stores in a scope start restoring after the component commits on the client and are not part of the global restateManager.ready. Wait for the scope’s own manager.ready in a loading boundary.

JavaScript / JSX
import { useEffect, useState } from 'react';
import { persistedRestate, useRestateScope } from '@kkwaenggwari/restate';

function Document({ documentId }) {
  const { manager, useDraft } = useRestateScope((scope) => ({
    manager: scope.manager,
    useDraft: persistedRestate('', { scope, namespace: 'documents', key: documentId }),
  }));
  const [state, setState] = useState('loading');

  useEffect(() => {
    let active = true;
    manager.ready.then(
      () => active && setState('ready'),
      () => active && setState('error'),
    );
    return () => {
      active = false;
    };
  }, [manager]);

  if (state === 'error') return <p>Could not load this document.</p>;
  if (state === 'loading') return <p>Loading document…</p>;
  return <Editor useDraft={useDraft} />;
}

Try it

Open and close documents and watch their state start fresh.

Preparing the example…

Owners outside React

When a service or server request owns the state, create the scope yourself and call destroy() when finished. Destroying a scope never deletes saved records.

JavaScript / JSX
import { createRestateScope, persistedRestate } from '@kkwaenggwari/restate';

export function createDocument(documentId) {
  const scope = createRestateScope();
  const useDraft = persistedRestate('', {
    scope,
    namespace: 'documents',
    key: documentId,
  });
  return {
    useDraft,
    manager: scope.manager,
    save: () => scope.manager.saveAll(),
    destroy: () => scope.destroy(),
  };
}

Search docs

Search titles, descriptions and text of every guide.