How to Use Storybook Preview API Hooks: A Complete Guide to useArgs, useState, and More
Storybook Preview API hooks let you manage state, side effects, and story configuration inside story functions and decorators using React-compatible APIs like useState, useArgs, and useEffect, all implemented through a custom HooksContext in the preview-api package.
Storybook's preview-api package provides a React-style hook system that runs inside the story rendering lifecycle. These hooks allow you to manipulate story arguments, global parameters, and local component state without leaving the story function context. All hooks are implemented in code/core/src/preview-api/modules/addons/hooks.ts and re-exported from storybook/preview-api.
What Are Storybook Preview API Hooks?
Storybook Preview API hooks are a custom implementation that mimics React's hook API but is designed specifically for Storybook's story execution context. Unlike standard React hooks that run inside React components, these hooks operate within story functions and decorators, giving you access to Storybook's internal channel, state management, and story metadata.
The hooks are backed by a HooksContext class that tracks the current render phase (MOUNT, UPDATE, or NONE), manages hook state across re-renders, and synchronizes updates through Storybook's event channel.
Complete List of Preview API Hooks
The following table lists every hook available in the preview-api package, its purpose, and its location in the source code:
| Hook | Purpose | Core Implementation |
|---|---|---|
useArgs |
Read and update story args | hooks.ts: 998‑1034 |
useCallback |
Memoize a callback function | hooks.ts: 43‑45 |
useChannel |
Subscribe/unsubscribe to channel events | hooks.ts: 14‑16, 30‑33 |
useEffect |
Register side‑effects that run after render | hooks.ts: 98‑104 |
useGlobals |
Read and update global args | hooks.ts: 49‑51 |
useMemo |
Memoize a computed value | hooks.ts: 22‑24 |
useParameter |
Retrieve a story parameter (or default) | hooks.ts: 86‑88 |
useReducer |
State management with a reducer function | hooks.ts: 57‑59 |
useRef |
Mutable ref object that survives re‑renders | hooks.ts: 41‑43 |
useState |
Simple state holder | hooks.ts: 34‑36 |
useStoryContext |
Access the full story context (id, parameters, args, globals, etc.) | hooks.ts: 60‑62 |
Importing Hooks from storybook/preview-api
All preview hooks are re-exported from the main entry point. Import them directly from 'storybook/preview-api':
import {
useArgs,
useCallback,
useChannel,
useEffect,
useGlobals,
useMemo,
useParameter,
useReducer,
useRef,
useState,
useStoryContext,
} from 'storybook/preview-api';
The export map is defined in code/core/src/preview-api/index.ts, which aggregates the hook implementations from code/core/src/preview-api/modules/addons/hooks.ts.
State Management Hooks
The preview API provides standard React-compatible hooks for managing local state within stories.
useState
useState stores values in a mutable ref and triggers re-renders via the internal update cycle. It is implemented in hooks.ts at lines 34-36.
import { useState } from 'storybook/preview-api';
export const Counter = () => {
const [count, setCount] = useState(0);
return (
<>
<p>Current count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increase</button>
</>
);
};
useReducer
For complex state logic, useReducer (lines 57-59 in hooks.ts) provides a reducer pattern similar to React.
import { useReducer } from 'storybook/preview-api';
type Action = { type: 'inc' } | { type: 'dec' };
const reducer = (state: number, action: Action) => {
switch (action.type) {
case 'inc': return state + 1;
case 'dec': return state - 1;
default: return state;
}
};
export const CounterWithReducer = () => {
const [count, dispatch] = useReducer(reducer, 0);
return (
<>
<p>{count}</p>
<button onClick={() => dispatch({ type: 'inc' })}>+</button>
<button onClick={() => dispatch({ type: 'dec' })}>‑</button>
</>
);
};
useRef
useRef (lines 41-43) creates a mutable container that persists across re-renders without triggering updates.
import { useRef, useState } from 'storybook/preview-api';
export const RefExample = () => {
const countRef = useRef(0);
const [, forceRender] = useState(0);
const increment = () => {
countRef.current += 1;
forceRender((n) => n + 1);
};
return <button onClick={increment}>Ref count: {countRef.current}</button>;
};
useMemo
For expensive calculations, useMemo (lines 22-24) caches computed values based on dependency arrays.
import { useMemo, useState } from 'storybook/preview-api';
export const ExpensiveCalc = () => {
const [num, setNum] = useState(0);
const result = useMemo(() => {
let sum = 0;
for (let i = 0; i < 1e6; i++) sum += Math.sqrt(num + i);
return sum;
}, [num]);
return (
<>
<p>Result: {result}</p>
<input type="number" value={num} onChange={(e) => setNum(+e.target.value)} />
</>
);
};
useCallback
useCallback (lines 43-45) memoizes callback functions to maintain stable references across renders.
import { useCallback, useState } from 'storybook/preview-api';
export const StableHandler = () => {
const [value, setValue] = useState(0);
const onClick = useCallback(() => setValue((v) => v + 1), []);
return <button onClick={onClick}>Clicked {value} times</button>;
};
Story Configuration Hooks
These hooks connect your story to Storybook's arg system, global parameters, and story-specific parameters.
useArgs
useArgs (lines 998-1034) allows stories to read and modify their own arguments, triggering Storybook's controls to update.
import { useArgs } from 'storybook/preview-api';
export const ArgsDemo = (args) => {
const [{ label }, updateArgs] = useArgs<{ label: string }>();
return (
<>
<p>Label prop: {label}</p>
<button onClick={() => updateArgs({ label: 'Updated!' })}>Change label</button>
</>
);
};
ArgsDemo.args = { label: 'Initial' };
useGlobals
useGlobals (lines 49-51) provides access to global Storybook state, such as theme or locale settings, across all stories.
import { useGlobals } from 'storybook/preview-api';
export const GlobalsDemo = () => {
const [globals, updateGlobals] = useGlobals();
return (
<>
<p>Current theme: {globals.theme ?? 'light'}</p>
<button onClick={() => updateGlobals({ theme: 'dark' })}>Dark mode</button>
</>
);
};
useParameter
useParameter (lines 86-88) retrieves story-specific parameters with an optional default value.
import { useParameter } from 'storybook/preview-api';
export const ParameterDemo = () => {
const myParam = useParameter<string>('myParam', 'fallback');
return <p>Parameter "myParam": {myParam}</p>;
};
ParameterDemo.parameters = { myParam: 'Hello world' };
Side Effects and Event Handling
Manage asynchronous operations and communicate with the Storybook manager using these hooks.
useEffect
useEffect (lines 98-104) registers side effects that run after the story renders, supporting cleanup functions.
import { useEffect, useState } from 'storybook/preview-api';
export const EffectDemo = () => {
const [time, setTime] = useState(Date.now());
useEffect(() => {
const id = setInterval(() => setTime(Date.now()), 1000);
return () => clearInterval(id);
}, []);
return <p>Current time: {new Date(time).toLocaleTimeString()}</p>;
};
useChannel
useChannel (lines 14-16, 30-33) subscribes to custom events on the Storybook channel and returns an emitter function.
import { useChannel } from 'storybook/preview-api';
export const ChannelDemo = () => {
const emit = useChannel({
'my-event': (msg) => console.log('Received:', msg),
});
return <button onClick={() => emit('my-event', 'Hello from story')}>Send Event</button>;
};
Accessing Story Metadata with useStoryContext
useStoryContext (lines 60-62) exposes the full story context including ID, title, args, and parameters.
import { useStoryContext } from 'storybook/preview-api';
export const ContextDemo = () => {
const { id, title, args } = useStoryContext();
return (
<pre>{JSON.stringify({ id, title, args }, null, 2)}</pre>
);
};
Architecture and Implementation Details
Understanding how the preview API hooks work under the hood helps debug complex story interactions and write efficient decorators.
HooksContext and the Hookify Pattern
The core engine resides in code/core/src/preview-api/modules/addons/hooks.ts. The HooksContext class maintains:
hookListsMap: AWeakMaplinking story functions to their hook listscurrentHooks: The active hook list during renderingnextHookIndex: Cursor tracking the current hook positioncurrentPhase: EitherMOUNT,UPDATE, orNONEcurrentEffects/prevEffects: Queues for effect scheduling
When a story executes, applyHooks wraps it with hookify, which swaps the global STORYBOOK_HOOKS_CONTEXT to the current HooksContext, runs the function, then restores the previous context. This mirrors React's render-with-current-context pattern.
Hook Lifecycle and Re-Rendering
During the MOUNT phase, each hook call creates a new Hook object and pushes it onto currentHooks. The callback executes immediately. During UPDATE, useHook pulls the next stored hook with getNextHook, validates ordering, and only re-executes callbacks when dependencies differ.
State-changing hooks (useState, useReducer, useArgs, useGlobals) call triggerUpdate, which sets hasUpdates = true. applyHooks then re-executes the story until no updates remain or the RENDER_LIMIT (25) is exceeded, preventing infinite loops.
Effect Handling and Cleanup
useEffect (lines 98-104) registers an Effect object. After rendering completes, HooksContext.triggerEffects runs the create function for new effects and calls destroy for removed ones. This guarantees deterministic cleanup analogous to React's effect model.
Channel Communication
State-changing hooks emit events on the global Storybook channel:
UPDATE_STORY_ARGS– dispatched byuseArgsRESET_STORY_ARGS– dispatched byuseArgsUPDATE_GLOBALS– dispatched byuseGlobalsFORCE_RE_RENDER– emitted when updates occur outside render cycles
The channel listens for STORY_RENDERED to flush pending effects via HooksContext.renderListener.
Error Guardrails
If a hook is called outside a story or decorator, invalidHooksError throws immediately, explaining that preview hooks must only be called within Storybook's execution context. This prevents accidental misuse in application code.
Summary
- Storybook Preview API hooks provide React-compatible state management and side-effect handling inside story functions and decorators.
- All hooks are implemented in
code/core/src/preview-api/modules/addons/hooks.tsand exported fromstorybook/preview-api. - State hooks (
useState,useReducer,useRef,useMemo,useCallback) work identically to React but useHooksContextinstead of React's internal dispatcher. - Configuration hooks (
useArgs,useGlobals,useParameter) connect stories to Storybook's args and globals system, emitting channel events on changes. - Effect hooks (
useEffect,useChannel) handle side effects and cross-window communication through the Storybook channel. - Context hook (
useStoryContext) exposes full story metadata including ID, title, and parameters. - The system prevents infinite loops with a
RENDER_LIMITof 25 and throwsinvalidHooksErrorwhen hooks are called outside Storybook's render cycle.
Frequently Asked Questions
Can I use preview API hooks outside of Storybook stories?
No. Preview API hooks rely on HooksContext, which is only initialized when Storybook renders a story or decorator. If you call these hooks outside this context, invalidHooksError throws immediately. For regular React components, use standard React hooks instead.
How do preview hooks differ from React hooks?
While the API is identical (same function signatures and dependency array behavior), Storybook's preview hooks use a custom HooksContext class instead of React's internal fiber architecture. This context tracks render phases (MOUNT vs UPDATE), manages a hookListsMap for each story function, and synchronizes state changes through Storybook's event channel rather than React's re-render cycle.
Why does my story re-render when I call updateArgs?
useArgs dispatches UPDATE_STORY_ARGS on the Storybook channel, which triggers a full story re-render to reflect the new argument values. This is intentional: args are the primary mechanism for controls, and changes must propagate to the component. To prevent unnecessary re-renders, ensure your component is memoized or use useMemo inside the story to stabilize derived values.
Can I use these hooks in decorators?
Yes. Preview API hooks work in both story functions and decorators because both execute within the HooksContext wrapper. Decorators often use useGlobals to read theme settings or useParameter to access configuration. When using hooks in decorators, remember they execute before the story, so state set in a decorator will be available when the story function runs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →