Getting started
React quickstart — declare a layer, mount an outlet, open and await a typed result.
Stop threading isOpen through props — declare the contract once, mount an outlet, and open from any descendant. This guide uses React; other adapters: Install.
Install
npm install @stainless-code/react-layerspnpm add @stainless-code/react-layersyarn add @stainless-code/react-layersbun add @stainless-code/react-layersThe core (@stainless-code/layers) is pulled in automatically and re-exported from the adapter.
Declare
Name the contract once — payload in, response out — with layerOptions:
import {
layerOptions,
type LayerComponentProps,
} from "@stainless-code/react-layers";
type ConfirmPayload = { title: string };
type ConfirmResponse = boolean;
function ConfirmDialog({
call,
payload,
}: LayerComponentProps<ConfirmPayload, ConfirmResponse>) {
return (
<div role="dialog">
<h2>{payload.title}</h2>
<button onClick={() => void call.end(true)}>Yes</button>
<button onClick={() => void call.end(false)}>No</button>
</div>
);
}
const confirm = layerOptions<ConfirmPayload, ConfirmResponse>({
stack: "confirm",
key: ["confirm", "remove"],
component: ConfirmDialog,
});import {
layerOptions,
} from "@stainless-code/react-layers";
function ConfirmDialog({
call,
payload,
}) {
return (
<div role="dialog">
<h2>{payload.title}</h2>
<button onClick={() => void call.end(true)}>Yes</button>
<button onClick={() => void call.end(false)}>No</button>
</div>
);
}
const confirm = layerOptions({
stack: "confirm",
key: ["confirm", "remove"],
component: ConfirmDialog,
});A typical layout keeps layer declarations in one folder so any component can import them:
- src/
- layers/
- confirm.ts
- toast.ts
- App.tsx
- main.tsx
- layers/
Mount
Wrap your app with StackProvider and render active layers with StackOutlet:
import { StackProvider, StackOutlet } from "@stainless-code/react-layers";
function App() {
return (
<StackProvider>
<StackOutlet stack="confirm" />
</StackProvider>
);
}import { StackProvider, StackOutlet } from "@stainless-code/react-layers";
function App() {
return (
<StackProvider>
<StackOutlet stack="confirm" />
</StackProvider>
);
}Open and await
Wire the declaration with useLayer(options) — payload-only open, inferred R, plus reactive state/queued/top:
import { useLayer } from "@stainless-code/react-layers";
function RemoveButton() {
const confirmLayer = useLayer(confirm);
async function handleRemove() {
const ok = await confirmLayer.open({ title: "Remove?" });
// ^? boolean
if (ok) {
/* … */
}
}
return (
<button type="button" onClick={() => void handleRemove()}>
Remove
</button>
);
}import { useLayer } from "@stainless-code/react-layers";
function RemoveButton() {
const confirmLayer = useLayer(confirm);
async function handleRemove() {
const ok = await confirmLayer.open({ title: "Remove?" });
// ^? boolean
if (ok) {
/* … */
}
}
return (
<button type="button" onClick={() => void handleRemove()}>
Remove
</button>
);
}Bag-form stays valid when you already hold a client: await client.open({ ...confirm, payload }). See Glossary for drive vs observe and Migration for the escape hatch.
Optional payload and response — see Awaiting results.