Skip to main content

Getting started with React

Verdant has React hooks generation. To enable it, pass --react to the CLI. A new module react.js will be emitted in the output directory. It exports one function, createHooks. Call it to construct hooks for your Verdant storage.

import { ClientDescriptor, ServerSync } from './client/index.js';
import { createHooks } from './client/react.js';
import migrations from './migrations.js';

const clientDesc = new ClientDescriptor({
namespace: 'todos',
migrations,
sync: {
authEndpoint: 'https://your.server/auth/sync',
initialPresence: {
emoji: '',
},
},
});

// export your generated hooks
export const hooks = createHooks();

It will generate named hooks based on each document collection, plus a few utility hooks. To see what collection query hooks are generated, see Query hooks. Other general hooks are as follows:

  • useClient: returns the Verdant Client instance. The best way to access the Client for document put and deletes. Suspends until the Client is ready.
  • useWatch: pass a "live document" to this and the component will update when that document changes.
  • useOnChange: similar to useWatch, but rather than re-rendering the component for you with new data, you instead pass a callback which is invoked. Does not re-render the component unless you update component state in the callback. You can use this to make imperative, outside-React changes if you want.
  • useSelf: returns your own presence.
  • usePeerIds: returns an array of string user IDs of peers. Good for iterating over peers to render them.
  • usePeer: pass a peer's user ID to retrieve their presence.
  • useViewId: pass a unique ID for a 'view' and the current replica's presence will be marked as 'on' that view.
  • useViewPeers: returns all peers on the same view as the current replica.
  • useField: pass an entity and a key, and this returns a bunch of useful stuff for working with a particular field. See below.
  • useSyncStatus: returns a boolean indicating whether sync is active or not.
  • useUndo: returns a function you can call to pop an item from the undo stack.
  • useRedo: returns a function you can call top pop an item from the redo stack.
  • useCanUndo: Returns true or false based on whether an item is available on the undo stack.
  • useCanRedo: Returns true or false based on whether an item is available on the redo stack.
  • useUnsuspendedClient: returns either a Client or null, if the Client has not yet initialized.

Context

In addition to the generated hooks you also get a Provider. Pass your ClientDescriptor instance to value to provide a client for your hooks to use.

By using a Context in this way, you can instantiate different clients for the same schema and change the library your app is interacting with. See the advanced usage below.

Typing of presence

By default, create hooks have any types for all presence values. To synchronize presence typings with your main client, provide the same Presence and Profile typings for both:

export interface Presence {
emoji: string;
}

export interface Profile {
// any data you may have put in profiles on the server
}

const clientDesc = new ClientDescriptor<Presence, Profile>({
// ...
});

// for React support, also pass the typing arguments to createHooks
export const hooks = createHooks<Presence, Profile>();

Custom mutation hooks

To create reusable hooks which utilize the client, you can chain .withMutations from the created hooks object and add your own custom hooks which take client as a first parameter.

This can help encapsulate custom behaviors, instead of ad-hoc calling useClient() and re-implementing them in multiple components.

Of course, you could do this in your own code; this is purely for convenience.

const hooks = createHooks<Presence, Profile>().withMutations({
useAddItem: (client) => {
return useCallback(
async (init: ItemInit) => {
const item = await client.items.put(init, { undoable: false });
analytics.reportItemCreated(item);
return item;
},
[client],
);
},
});