React
@w6w/react puts w6w into a React app: one provider that holds the client, and hooks that load
data and run things from your components. It’s built on the Node SDK, so
anything without a hook is one useW6WClient() call away.
Before you start
- A React 18 or later app.
- A way to get a w6w token in the browser. For a quick test, an API token from Studio → Settings → API tokens works. For real users, mint per-user tokens on your backend. See Next.js and token minting.
1. Install the package
npm install @w6w/react react
This also installs @w6w/sdk. If you import from it directly, for example ApiError, add it to
your own dependencies at the same version as @w6w/react: npm install @w6w/sdk.
2. Wrap your app in the provider
The browser has no environment variables, so pass the base URL and token as props:
import { W6WProvider } from "@w6w/react";
export function App() {
return (
<W6WProvider baseUrl="https://<your-host>" token={() => getToken()}>
<Dashboard />
</W6WProvider>
);
}
token takes a string, or a function that returns one. The function may be async. It is called
whenever a request needs a token, and concurrent requests share one in-flight call. The result is
not cached once it settles, so a rotated token is picked up on the next request. Until it returns a token, read
hooks stay in their loading state instead of sending a request without one.
For per-user sessions minted by your backend, pass getSession instead of token: one callback
that calls your session endpoint. The provider caches the session, renews it before it expires and
re-checks on tab resume, and useW6WSession() reports its status. It needs @w6w/react 0.9.13 or
later. See Next.js and token minting.
3. Read data with a hook
import { useWorkflows } from "@w6w/react";
function Dashboard() {
const { data, loading, error, refetch } = useWorkflows();
if (loading) return <p>Loading…</p>;
if (error) return <p>Failed: {String(error)}</p>;
return (
<>
<button type="button" onClick={refetch}>Refresh</button>
<ul>
{data?.map((wf) => <li key={wf.id}>{wf.displayName || wf.name}</li>)}
</ul>
</>
);
}
You should see your workflows listed. Every read hook returns data, error, loading and
refetch, loads when the component mounts, and reloads when its arguments change.
4. Run something from a component
Mutation hooks return a call function plus data, error and loading. Nothing runs until you
call it:
import { useRunWorkflow } from "@w6w/react";
function RunButton({ workflowId }: { workflowId: string }) {
const { call, data, error, loading } = useRunWorkflow();
async function onClick() {
try {
await call(workflowId, { input: { source: "dashboard" } });
} catch {
// the error is also in `error`
}
}
return (
<>
<button type="button" disabled={loading} onClick={onClick}>Run</button>
{data && <p>Run {data.runId}: {data.status}</p>}
{error ? <p>Failed: {String(error)}</p> : null}
</>
);
}
useRunWorkflow waits for the run to finish by default. Pass wait: false to only queue it. To run
an action, Function, Endpoint or Workflow by id, use useRun(), which wraps client.run().
A mutation’s call rejects when it fails, and also puts the error in error. Catch it, or React
reports an unhandled rejection.
Mutations don’t refresh your lists. After a create, update or delete, call the matching read
hook’s refetch.
Use the client directly
For anything without a hook, such as running a Function by key, calling an Endpoint, cancelling a
run, managing your team, or reaching a route with request(), take the client from context:
import { useW6WClient } from "@w6w/react";
function SendWelcome({ email }: { email: string }) {
const client = useW6WClient();
return (
<button
type="button"
onClick={() => client.functions.run("send-welcome-email", { payload: { to: email } })}
>
Send welcome email
</button>
);
}
useW6WClient() returns the same W6WClient the hooks use. Everything on the
Node SDK pages works on it, including client.request() for routes no method
wraps yet.
Edit workflows with a typed shape
The Node SDK returns a workflow definition as an untyped object, so that a newer server’s extra
fields never break your app. If you pass definitions to or from the @w6w/ui flow editor,
convert them with toFlowWorkflow and fromFlowWorkflow rather than casting:
import { fromFlowWorkflow, toFlowWorkflow, WorkflowShapeError } from "@w6w/react";
const { workflow } = await client.workflows.get(id);
try {
const flow = toFlowWorkflow(workflow);
// Give `flow` to the editor, and get an edited copy back.
await client.workflows.update(id, fromFlowWorkflow(edited));
} catch (err) {
if (err instanceof WorkflowShapeError) console.error(err.issues); // [{ path, message }]
}
toFlowWorkflow checks that manifestVersion, id and name are strings and that steps is
an array of objects that each have a string id. If edges, settings or variables are
present, they must be an array, an object and an array. When something is wrong, the error’s
issues lists every problem, each with a path and a message. It doesn’t look inside a step’s
uses. Keys it doesn’t know about, such as retry, onError or your own x- fields, pass through
unchanged in both directions, so converting there and back gives you the same definition.
createW6WUiAdapter is tested at run time against the @w6w/ui contract it implements: every
member is present as a function, forwards its arguments to the expected request, and hands the
server’s error body to the caller as err.body.
The adapter is the supported way to connect the UI to w6w. Most of its members run on the
Studio-internal client.console.* calls, which can change without notice, so pass
createW6WUiAdapter(client) to the UI as it is. Don’t call the console namespace yourself or add
members to the adapter. listAppsByIds and listConnections use the public
client.apps.byIds and client.connections.list. For a workflow or Function editor, see
Embed w6w in your product.
Handle errors
Hook errors are the SDK’s ApiError, with status, code, message and raw:
import { ApiError } from "@w6w/sdk";
function errorText(error: unknown): string {
if (error instanceof ApiError) {
if (error.status === 401) return "Your session expired. Sign in again.";
if (error.status === 0) return "Can't reach w6w right now.";
return error.message;
}
return String(error);
}
Nothing is retried automatically. To recover from an expired token, set refreshOnUnauthorized on
the provider, as shown in Next.js and token minting. For what each status
and code means, see Handle errors.
Troubleshooting
| Symptom | Cause | What to do |
|---|---|---|
Hooks stay loading: true forever | The token supplier returns nothing, or ready is false. | Return a token string, and set ready once you have one. |
useW6WClient must be used inside <W6WProvider> | The component isn’t under the provider. | Move <W6WProvider> higher in the tree. |
| A list doesn’t update after a create | Mutations don’t refresh reads. | Call the read hook’s refetch after call resolves. |
401 after switching users | The client still holds the previous user’s token. | Set identityKey to the signed-in user’s id so the provider rebuilds the client. |
network_error in the browser, but the same call works from a terminal | A proxy in front of w6w answered with an error page the browser blocked, or baseUrl points somewhere else. | Check baseUrl, then retry. If it persists, open the request in the browser’s network tab to see the real status. |
Where to next
- Hooks reference: every hook and every provider prop.
- Next.js and token minting: per-user tokens from your backend.
- Node SDK: the client under the hooks.