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: A WeakMap linking story functions to their hook lists
  • currentHooks: The active hook list during rendering
  • nextHookIndex: Cursor tracking the current hook position
  • currentPhase: Either MOUNT, UPDATE, or NONE
  • currentEffects / 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 by useArgs
  • RESET_STORY_ARGS – dispatched by useArgs
  • UPDATE_GLOBALS – dispatched by useGlobals
  • FORCE_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.ts and exported from storybook/preview-api.
  • State hooks (useState, useReducer, useRef, useMemo, useCallback) work identically to React but use HooksContext instead 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_LIMIT of 25 and throws invalidHooksError when 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →