@byearlybird/starling-react
v0.4.0
Published
React bindings for Starling: a provider for the session, and hooks for live queries, sync status and files
Downloads
75
Readme
React
@byearlybird/starling-react: React bindings for a Starling app opened with @byearlybird/starling-client's openClient. @byearlybird/starling-client and React 19 are peer dependencies. See decision 0009.
| Export | File | Contents |
| ----------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| ClientProvider, useClient | src/client.tsx | The provider for an opened Client, and the hook that reads it with its session, actions and encryption actions |
| useSyncStatus | src/client.tsx | What a space's sync is doing, kept current |
| useQuery | src/use-query.ts | A live read: reruns after commits to the collections it read |
| useFileStatus | src/use-file-status.ts | Where an attached file is: uploading, on the relay, or still on its way from another device |
import { createRoot } from "react-dom/client";
import { absurd } from "@byearlybird/starling-client";
import { ClientProvider, useQuery, useClient } from "@byearlybird/starling-react";
import { client } from "./client"; // await openClient(...)
function Todos() {
const { space } = useClient<typeof client>();
const todos = useQuery(
space,
(tx) =>
tx.query("todos", { filter: (q) => q.eq("done", false), orderBy: { field: "createdAt" } }),
[],
);
if (todos.kind !== "ready") return <p>Loading…</p>;
return (
<ul>
{todos.data.data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
function Account() {
const { session, actions } = useClient<typeof client>();
switch (session.kind) {
case "signed-out":
return <SignIn onEmail={actions.requestCode} onCode={actions.verifyCode} />;
case "signed-in":
return (
<button onClick={() => void actions.signOut()}>Sign out {session.account.email}</button>
);
case "starting":
case "ended":
return null;
default:
return absurd(session);
}
}
// With `encryption: passphraseEncryption()`, `session.encryption` says what the passphrase needs,
// and `useClient().encryption` has its actions:
function Passphrase() {
const { session, encryption } = useClient<typeof client>();
if (session.kind !== "signed-in") return null;
switch (session.encryption.kind) {
case "needs-passphrase":
return <NewPassphrase onSubmit={encryption.setUpPassphrase} />;
case "locked":
return <Unlock onSubmit={encryption.unlock} onForgot={encryption.resetPassphrase} />;
case "ready":
case "unavailable":
return null;
default:
return absurd(session.encryption);
}
}
createRoot(document.getElementById("root")!).render(
<ClientProvider client={client}>
<Account />
<Todos />
</ClientProvider>,
);The provider never opens or closes the session: the app does, once, so StrictMode and remounts keep it. useQuery(db, read, deps) works like useEffect: it starts over, from a loading state, only when db or a value in deps changes. Its state's kind is loading, ready with data, or error with the error and the last data. pnpm build writes the published dist/.
Requirements
Session (client.tsx)
- Components anywhere below the provider can read the space, the session and its actions, and rerender when the session changes
- Components can watch what a space's sync is doing, such as needing an app update
- Mounting, unmounting and StrictMode never open or close the session
- Using the session without a provider fails with a clear error
useQuery (use-query.ts)
- Components can watch any read across one or more collections and receive data, loading, and error state
- A read reruns when its declared dependencies change, and not when only its callback is recreated
- Changing a dependency exposes loading state instead of results from the previous read
- Read errors are exposed with the last successful data, and changing dependencies allows recovery
- Unmounting a component or changing its dependencies stops its previous observation
useFileStatus (use-file-status.ts)
- Components can watch where a file is, and the status updates as it uploads or appears elsewhere
- A status from one file never shows for another
