Shared state
@screenplay.space/state bridges a piece of your app’s state to the canvas.
Change it in one person’s frame and every other viewer’s frame updates too.
It’s useful for things like the signed-in user, the selected plan, or a
billing toggle, so a whole team can look at the same screen in the same state.
You don’t need the package for the route and scroll position; every frame already shares those, and a frame follows another viewer’s route without reloading (see Frames). Use it for state the page keeps itself.
The route in the selected frame’s address bar shows a {} when shared state
is present. Hover it to
see the JSON. Screenplay doesn’t edit shared state from the canvas; it only
displays it.
The agent knows this package through the screenplay-share-state
skill. Try “share the billing toggle with the canvas”.
Install
npm install @screenplay.space/stateReact 17 or later is a peer dependency.
Two-way sync
Pass the value and its setter to keep existing UI state in sync in both directions:
import { useState } from "react"
import { useSharedState } from "@screenplay.space/state"
export function BillingToggle() {
const [annual, setAnnual] = useState(false)
useSharedState("billing", annual ? "annual" : "monthly", (v) =>
setAnnual(v === "annual")
)
return <Toggle value={annual} onChange={setAnnual} />
}This is the demo app’s pricing page. Flip the toggle in one frame and every
frame on /pricing flips with it.
Publish only
Leave out the setter to publish a value the canvas can see, but never write back:
const user = useUser()
useSharedState("user", user ? { id: user.id, role: user.role } : null)Share a whole store
If your app keeps its state in a zustand store,
share the whole store with one call instead of one useSharedState per value:
import { create } from "zustand"
import { shareStore } from "@screenplay.space/state"
export const useSales = create((set) => ({
contactOpen: false,
plan: "Growth",
seats: 5,
open: (plan) => set({ contactOpen: true, plan }),
close: () => set({ contactOpen: false }),
setSeats: (seats) => set({ seats }),
}))
shareStore("sales", useSales)Open the contact-sales modal in one frame and pick 12 seats, and every other
viewer’s frame shows the same modal with the same seats. The store’s data is
shared under the one key; changes from other viewers are merged back with
setState, so the store’s actions keep working everywhere.
Only fields that are plain JSON are shared. Actions, and fields holding a
Date, Map, Set or class instance, stay local and are never overwritten.
shareStore returns a function that stops sharing.
Without React
import {
setSharedState,
subscribeSharedState,
clearSharedState,
} from "@screenplay.space/state"
const remove = setSharedState("session", { id: "u_1", role: "admin" })
const unsubscribe = subscribeSharedState("session", (next) => {
console.log("session changed", next)
})
remove() // or clearSharedState("session")
unsubscribe()On a mockup
A mockup’s page has no bundler, so it doesn’t install the
package. It already has a global screenplay.shareState(key, initial, onChange)
that follows the same rules. onChange runs at once with the current value
and again on every change, your own set included, so one function can draw
the page:
<script>
const tab = screenplay.shareState("tab", "overview", (value) => {
document.body.dataset.tab = value
})
document.querySelector("#plans").onclick = () => tab.set("plans")
</script>You won’t usually write this yourself: the chat that drew the mockup adds it when you ask for a page that remembers what people clicked.
Rules
- Values must be JSON. Functions, class instances,
undefined, and circular references are dropped. - The combined state is capped at 64 KB. Larger updates are skipped with a console warning.
- State is kept with the frame across reloads and carries into
play mode. Keep keys stable, like
ids: renaming a key drops the old entry. - The room’s state wins when a frame loads. A frame that loads or reloads first asks the canvas for the room’s state and waits for the answer before publishing, so its initial values don’t overwrite what other viewers picked. Keys the room doesn’t have yet take the frame’s value. If the canvas doesn’t answer within half a second, the frame publishes anyway.
- Like knobs, it’s development-only and does nothing outside a Screenplay frame, so it’s safe to ship. It works in Vite and other bundlers with no setup, like knobs.