Build the app-facing side of a Runtime Kit feature: one React surface reads named data, renders every state, and asks a named target to do work from a button.
Named data means Runtime Kit has already made a piece of Bitfield data available to this app surface. The component reads it. It does not create storage addresses or parse package files.When to use this
Use this recipe when a screen needs both data and an action.| Good fit | Not this recipe |
|---|
| A welcome panel that reads copy and asks for a refresh. | A package file with no UI yet. |
| A search panel that reads prepared results and calls a search target. | A pure reference page for the hook signature. |
| A dashboard card that reads prepared metrics and triggers a target. | A component that imports package files directly. |
The point is the separation: the component owns UI state and user actions. Runtime Kit owns the public read/request line. The package owns the material served through that line.What you will build
| Contract part | Value |
|---|
| React surface | WelcomePanel |
| Data name | welcome |
| Read call | useBitfieldData<WelcomeData>('welcome') |
| Target | product.search |
| Request payload | { reason: 'refresh-welcome-panel' } |
| UI states | loading, error, empty, success, request success, request failure |
| Public imports | @bitfield/runtime-kit, @bitfield/runtime-kit/react |
Component
import { sendRequestToBitfieldTarget } from '@bitfield/runtime-kit';
import { useBitfieldData } from '@bitfield/runtime-kit/react';
import { useState } from 'react';
type WelcomeData = {
headline: string;
body: string;
};
export function WelcomePanel() {
const welcome = useBitfieldData<WelcomeData>('welcome');
const [requestMessage, setRequestMessage] = useState<string | null>(null);
const [requestError, setRequestError] = useState<string | null>(null);
async function refresh() {
setRequestMessage(null);
setRequestError(null);
try {
await sendRequestToBitfieldTarget({
target: 'product.search',
payload: { reason: 'refresh-welcome-panel' },
});
setRequestMessage('Refresh request sent.');
} catch (error) {
setRequestError(error instanceof Error ? error.message : 'Refresh request failed.');
}
}
if (welcome.loading) return <section>Loading welcome panel.</section>;
if (welcome.error) return <section>Could not load this panel.</section>;
if (!welcome.data) return <section>No welcome copy yet.</section>;
return (
<section>
<h2>{welcome.data.headline}</h2>
<p>{welcome.data.body}</p>
<button type="button" onClick={refresh}>
Refresh
</button>
{requestError ? <p>{requestError}</p> : null}
{requestMessage ? <p>{requestMessage}</p> : null}
</section>
);
}
Read path
The read line is:const welcome = useBitfieldData<WelcomeData>('welcome');
That line means:| Piece | Meaning |
|---|
WelcomeData | The shape this component expects to render. |
'welcome' | Public data name for this surface. |
welcome.loading | Runtime Kit is still preparing the value. |
welcome.error | Runtime Kit cannot provide the value. |
welcome.data | The current prepared value, or null. |
The selector is not a storage path. The component does not import the package record, file, or derived value that produced the data name.Request path
The request line is:await sendRequestToBitfieldTarget({
target: 'product.search',
payload: { reason: 'refresh-welcome-panel' },
});
That line means:| Piece | Meaning |
|---|
target | Public callable name declared by package material. |
payload | App-owned request data Runtime Kit converts to bytes. |
try/catch | UI handles request failure visibly. |
requestMessage | UI confirms the action happened. |
The component does not import the target implementation.Verify the recipe
Use this checklist before copying the surface into a real app:| Check | Pass condition |
|---|
| Public imports | Only imports Runtime Kit from @bitfield/runtime-kit and @bitfield/runtime-kit/react. |
| Data name | Reads welcome through useBitfieldData(...). |
| Loading state | Shows a visible loading message. |
| Error state | Shows a visible read error message. |
| Empty state | Shows a visible empty-state message instead of returning null. |
| Success state | Renders headline and body. |
| Request path | Calls product.search through sendRequestToBitfieldTarget(...). |
| Request failure | Catches request errors and renders a visible message. |
What should happen
The surface can render before data is ready.
The surface can show failed reads.
The surface can show missing data.
The surface can render real named data.
The button can ask product.search for work without importing package implementation code.
Bad version
This version looks shorter, but it hides real states and crosses the boundary later:if (!welcome.data) return null;
// Later:
// import { runSearchDirectly } from '../packages/search-package/slot';
That is how tangled apps start. The UI disappears when data is missing, and the button is one direct import away from bypassing Runtime Kit.Common failures
| Symptom | Cause | Fix |
|---|
| Blank panel | Empty state returns null. | Render a visible empty state. |
| User sees no error | Component ignores welcome.error or request errors. | Render read and request failures separately. |
| Button works only in local dev | Component imported a target implementation directly. | Call the public action name. |
| Data shape crashes render | Component assumed fields that the data name does not provide. | Align WelcomeData with the public data name contract. |
| Component code creates storage addresses | The hook was treated like a database query. | Move back to the data name contract. |
Review checklist
The finished surface should answer:Data name:
Action name:
Request payload:
Read states rendered:
Request states rendered:
Public imports used:
Files changed:
Reject the answer if it mentions package implementation imports, local state hand edits, hidden providers, or storage address construction inside the component.Why the component stays small
The component has two jobs:
- Read the prepared
welcome data.
- Ask
product.search to do work.
It does not import the record location, package byte path, target runner, or active package-set mount code.What changes when the package changes
The package can add more records, ship more bytes, or replace the implementation for product.search. The component can stay the same as long as the public data shape and action name stay the same.Next
Read Read data in React for the hook contract.Read Send a request for payload conversion, reply bytes, cancellation, and request errors.Read Runtime Kit API for exact useBitfieldData(...) and sendRequestToBitfieldTarget(...) contracts.Read Package to screen for the full package-to-surface path.