Shared Activity state
Synchronize optimistic state across everyone in one Discord Activity instance.
Shared Activity state combines a WebSocket route with an optimistic React state hook. Values are scoped to one verified Discord Activity instance and protected by server validation, size limits, revisions, and room cleanup.
#Define the server state
1import { defineSharedActivityState } from "ludicord/ws/server";23interface GameState {4 round: number;5 score: number;6}78function isGameState(value: unknown): value is GameState {9 if (typeof value !== "object" || value === null) return false;10 const game = value as Partial<GameState>;11 return Number.isSafeInteger(game.round) && Number.isSafeInteger(game.score);12}1314export default defineSharedActivityState<GameState>({15 initialState: { round: 1, score: 0 },16 validate: isGameState,17});
The server refuses values that fail validation, cannot be serialized as JSON, or exceed the configured byte limit.
#Use the state in React
1import { useSharedActivityState } from "ludicord/activity";23export default function embed() {4 const game = useSharedActivityState("/ws/game", "main", { round: 1, score: 0 });56 return (7 <button disabled={game.status !== "open"} onClick={() => game.setValue((value) => ({ ...value, score: value.score + 1 }))}>8 Score {game.value.score} · revision {game.revision}9 </button>10 );11}
setValue has the same value-or-updater shape as React state. It updates the local UI immediately, then sends the value using the last acknowledged revision.
#Understand synchronization
If two clients write from the same old revision, the server sends the current value to the stale client. The hook reconciles and flushes a newer pending local value when one exists.
#Use multiple keys
The route stores independent values by key inside the Activity room.
1const board = useSharedActivityState("/ws/game", "board", emptyBoard);2const timer = useSharedActivityState("/ws/game", "timer", 60);
Keys must contain 1–128 characters. Keep them stable; changing the key subscribes the hook to a different value.
#Configure room safety
1export default defineSharedActivityState({2 initialState: ({ key, client }) => createInitialState(key, client.ludicord.user.id),3 validate: isGameState,4 maxRooms: 1_000,5 roomTtl: 60 * 60 * 1_000,6 maxValueBytes: 64 * 1024,7});
The built-in store is process memory. For state that must survive deployment or synchronize across multiple server processes, persist authoritative data in a shared database and use WebSockets as the live transport.
#Choose shared state deliberately
Use React useState for one player's temporary UI, useActivityStorage for browser-persisted preferences, useActivityQuery for server data, and shared Activity state for small values everyone in one live instance needs now.
#Next step
Continue to Data fetching for cached HTTP data that has a different lifetime from a live WebSocket value.