React API
The full type surface of @playhtml/react. For a gentler introduction, see Using React. For concept-by-concept usage, each page under Data and Capabilities has a React tab alongside the vanilla form.
<PlayProvider>
Section titled “<PlayProvider>”Initializes the playhtml client for a React subtree. There must be exactly one PlayProvider per React root.
interface PlayProviderProps {
initOptions?: InitOptions;
pathname?: string;
children: React.ReactNode;
}
Everything in initOptions maps one-to-one onto the vanilla playhtml.init() argument — see the init options reference.
pathname is optional and only needed for client-side-navigation frameworks (React Router, Next.js, etc.) where the browser Navigation API isn’t available. Pass it from your router and playhtml will rebuild rooms + rescan the DOM on pathname changes. See navigation for details.
import { PlayProvider } from "@playhtml/react";
<PlayProvider initOptions={{ cursors: { enabled: true } }}>
<App />
</PlayProvider>;
withSharedState(config, render)
Section titled “withSharedState(config, render)”HOC that returns a component with live, shared data plus a setData callback. The config controls how the element is wired into playhtml; the render function is a regular functional component that receives playhtml’s state as its first argument and your own props as its second.
Signatures
Section titled “Signatures”withSharedState<T, V, P>(
config: WithSharedStateConfig<T, V> | ((props: P) => WithSharedStateConfig<T, V>),
render: (
playhtmlProps: ReactElementEventHandlerData<T, V>,
componentProps: P,
) => React.ReactNode,
): React.ComponentType<P>;
Config shape
Section titled “Config shape”interface WithSharedStateConfig<T, V> {
defaultData: T;
myDefaultAwareness?: V;
id?: string;
tagInfo?: TagType[];
}
defaultData— required. The initial value ofdata. Survives reload.myDefaultAwareness— optional. Initial value for this user’s ephemeral per-user field. Does not persist.id— optional. Stable id for the element. If omitted, playhtml derives one from the rendered DOM; see Dynamic elements for why stable ids matter.tagInfo— optional. Marks the element as one of the built-in capabilities (e.g.[TagType.CanToggle]). See Capabilities.
Render-function props
Section titled “Render-function props”interface ReactElementEventHandlerData<T, V> {
data: T;
setData: (data: T | ((draft: T) => void)) => void;
awareness: V[];
myAwareness?: V;
setMyAwareness: (data: V) => void;
ref: React.RefObject<HTMLElement>;
}
setData accepts either a replacement value or a mutator function. See Data essentials for the merge semantics.
Props-dependent config
Section titled “Props-dependent config”Pass a callback instead of a config object when defaultData needs to derive from props:
export const Reaction = withSharedState(
({ reaction: { count } }) => ({ defaultData: { count } }),
({ data, setData }, props) => /* … */,
);
<CanPlayElement>
Section titled “<CanPlayElement>”Component form of withSharedState. Useful when you want JSX children (render-prop style) instead of wrapping a component, or when you need ref access to a specific element.
interface CanPlayElementProps<T, V> {
id?: string;
defaultData: T;
myDefaultAwareness?: V;
tagInfo?: TagType[];
standalone?: boolean;
children: (props: ReactElementEventHandlerData<T, V>) => React.ReactNode;
}
id— required if the top-level child is a React Fragment. Otherwise defaults to the child’s id, or a hash of the child’s content.standalone— whentrue, this element doesn’t inherit defaults from any built-in capability (it’s a purecan-playelement).
<CanPlayElement
tagInfo={[TagType.CanToggle]}
id="my-lamp"
defaultData={{ on: false }}
>
{({ data, setData }) => (
<button onClick={() => setData({ on: !data.on })}>
{data.on ? "on" : "off"}
</button>
)}
</CanPlayElement>
<CanMoveElement>
Section titled “<CanMoveElement>”Typed wrapper around CanPlayElement for draggable elements. Accepts the same dataSource, shared, and standalone props, plus three bounds props for constraining the drag area.
interface CanMoveElementProps {
bounds?: string;
boundsMinVisible?: number;
boundsMinVisiblePx?: number;
dataSource?: string;
shared?: boolean | string;
standalone?: boolean;
children: React.ReactElement | ((data: MoveEventData) => React.ReactElement);
}
bounds— id or CSS selector of the container to keep the element inside."arena","#arena", and".grid"all work.boundsMinVisible— fraction (0–1) of the element that must stay insideboundson every edge. Default0.25. Use1to pin the element fully inside,0to drop the fraction constraint entirely.boundsMinVisiblePx— absolute pixel floor on the keep-visible slice. Default60. Useful when an image has transparent padding around its paint — a pure fraction of the layout bbox might otherwise let the visible pixels clip into invisible border.
The effective keep-visible slice on each axis is max(boundsMinVisible × size, boundsMinVisiblePx). Set both knobs to 0 to opt fully out of the keep-visible guarantee. See can-move in the capabilities reference for the interaction details.
import { CanMoveElement } from "@playhtml/react";
<div id="fridge" style={{ position: "relative", height: 400 }}>
<CanMoveElement bounds="fridge">
<div id="magnet-a">🍎</div>
</CanMoveElement>
<CanMoveElement bounds="fridge" boundsMinVisible={0.5} boundsMinVisiblePx={0}>
<div id="magnet-b">🥐</div>
</CanMoveElement>
</div>;
usePlayContext()
Section titled “usePlayContext()”Access the playhtml context from any descendant of PlayProvider.
interface PlayContextValue {
hasSynced: boolean;
cursors: CursorsView;
configureCursors: (opts: Partial<CursorOptions>) => void;
getMyPlayerIdentity: () => PlayerIdentity;
registerPlayEventListener: (type: string, handler: PlayEvent) => string;
removePlayEventListener: (type: string, id: string) => void;
dispatchPlayEvent: (msg: { type: string; eventPayload?: unknown }) => void;
}
hasSynced
Section titled “hasSynced”Boolean that flips to true once the initial state from the server has landed. Useful for gating effects that should run exactly once per synced session:
const { hasSynced } = usePlayContext();
useEffect(() => {
if (hasSynced) setData({ count: data.count + 1 });
}, [hasSynced]);
cursors
Section titled “cursors”A reactive view of the cursor system. Components using this re-render when colors or identities change.
const { cursors } = usePlayContext();
// cursors.allColors: string[]
// cursors.color: string
// cursors.name: string
See Cursors for the full cursor configuration surface.
Event API
Section titled “Event API”const {
registerPlayEventListener,
removePlayEventListener,
dispatchPlayEvent,
} = usePlayContext();
Usually you’ll wrap these in a hook to bind a listener to the component’s lifecycle — see Events for the useConfetti pattern.
TagType
Section titled “TagType”Re-exported from @playhtml/common. Use these as tagInfo entries when you want a built-in capability (can-move, can-toggle, etc.) wired into your component.
import { TagType } from "@playhtml/common";
TagType.CanPlay;
TagType.CanMove;
TagType.CanToggle;
TagType.CanGrow;
TagType.CanSpin;
TagType.CanHover;
TagType.CanDuplicate;
TagType.CanMirror;
Examples
Section titled “Examples”The repo has a collection of runnable React examples at packages/react/examples. Live versions are visible at playhtml.fun/experiments/one/ and playhtml.fun/experiments/two/.
Open considerations
Section titled “Open considerations”A few things still in flux in the React package:
- Per-key persistence config. Currently persistence is a whole-store choice:
setMyAwarenessfor ephemeral,setDatafor persistent, no local-only mode. A futurepersistenceOptionsobject might let you configure per-key (none/local/global). awarenesssplitting.awarenesscurrently includes the local user; it may split intomyAwareness+othersAwarenessfor clarity.- Hook ergonomics. A pure-hook interface (
useSharedState({ id, defaultData })) is being evaluated as an alternative to the HOC form. The blocker is that hooks have no natural place to pin a stableid.