createStore
The createStore function creates a Zustand store that automatically syncs with the view’s persistent state. Unlike useViewState, this provides a full Zustand store with actions and middleware support, making it ideal for complex state management scenarios.
Basic usage
Section titled “Basic usage”import { createStore } from "@usefractal/frac/web";import type { StateCreator } from "zustand";
type CounterState = { count: number; increment: () => void; decrement: () => void;};
const useCounterStore = createStore<CounterState>((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })),}));
function CounterView() { const count = useCounterStore((state) => state.count); const increment = useCounterStore((state) => state.increment); const decrement = useCounterStore((state) => state.decrement);
return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> );}Parameters
Section titled “Parameters”storeCreator
Section titled “storeCreator”storeCreator: StateCreator<State, [], [], State>;A Zustand state creator function that defines your store’s initial state and actions. This follows the standard Zustand pattern.
defaultState
Section titled “defaultState”defaultState?: State | (() => State)Optional default state to use if no persisted state exists. Can be:
- A value of type
State - A function that returns
State(lazy initialization)
If window.openai.widgetState exists, it will take precedence over defaultState.
Returns
Section titled “Returns”UseBoundStore<StoreApi<State>>;A Zustand store that can be used with the standard Zustand hooks:
store.getState()- Get the current statestore.setState()- Update the statestore.subscribe()- Subscribe to state changesuseStore(selector)- React hook to access the store
Features
Section titled “Features”Automatic State Persistence
Section titled “Automatic State Persistence”State changes are automatically persisted to window.openai.setWidgetState. The state is serialized (functions are stripped) before persistence.
State Initialization Priority
Section titled “State Initialization Priority”The store initializes state in the following order:
window.openai.widgetState(if available) - highest prioritydefaultStateparameter (if provided)- State returned by
storeCreator
Examples
Section titled “Examples”Todo List with Actions
Section titled “Todo List with Actions”import { createStore } from "@usefractal/frac/web";import type { StateCreator } from "zustand";
type Todo = { id: string; text: string; completed: boolean;};
type TodoState = { todos: Todo[]; addTodo: (text: string) => void; toggleTodo: (id: string) => void; removeTodo: (id: string) => void; clearCompleted: () => void;};
const useTodoStore = createStore<TodoState>((set) => ({ todos: [], addTodo: (text) => set((state) => ({ todos: [ ...state.todos, { id: crypto.randomUUID(), text, completed: false }, ], })), toggleTodo: (id) => set((state) => ({ todos: state.todos.map((todo) => todo.id === id ? { ...todo, completed: !todo.completed } : todo ), })), removeTodo: (id) => set((state) => ({ todos: state.todos.filter((todo) => todo.id !== id), })), clearCompleted: () => set((state) => ({ todos: state.todos.filter((todo) => !todo.completed), })),}));
function TodoView() { const todos = useTodoStore((state) => state.todos); const addTodo = useTodoStore((state) => state.addTodo); const toggleTodo = useTodoStore((state) => state.toggleTodo); const removeTodo = useTodoStore((state) => state.removeTodo); const clearCompleted = useTodoStore((state) => state.clearCompleted); const [input, setInput] = useState("");
const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { addTodo(input.trim()); setInput(""); } };
return ( <div className="todo-view"> <h3>Todo List</h3> <form onSubmit={handleSubmit}> <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Add a todo..." /> <button type="submit">Add</button> </form> <ul> {todos.map((todo) => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? "line-through" : "none", }} > {todo.text} </span> <button onClick={() => removeTodo(todo.id)}>Remove</button> </li> ))} </ul> <button onClick={clearCompleted}>Clear Completed</button> </div> );}Comparison with useViewState
Section titled “Comparison with useViewState”| Feature | createStore | useViewState |
|---|---|---|
| Type | Zustand store | React hook |
| Use Case | Complex state with actions | Simple state updates |
| Actions | Supported | Not supported |
| Middleware | Can be composed | Not supported |
| Selectors | Full Zustand selectors | Returns full state |
| Performance | Fine-grained subscriptions | Re-renders on any change |
| API | Zustand API | React useState-like API |
Use createStore when you need:
- Actions and methods on your state
- Complex state logic
- Fine-grained subscriptions
- Zustand middleware support
Use useViewState when you need:
- Simple state management
- React
useState-like API - Quick setup without Zustand knowledge