Skip to main content
The Actions Addon allows you to display and monitor event data from your component interactions. It’s essential for testing callbacks, event handlers, and understanding component behavior during development.

Installation

1

Install the addon

npm install -D @storybook/addon-ondevice-actions
2

Register the addon

Add the addon to your .rnstorybook/main.ts configuration:
.rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native';

const main: StorybookConfig = {
  addons: ['@storybook/addon-ondevice-actions'],
};

export default main;

Basic Usage

The most common way to use actions is with the fn() function from storybook/test, which creates a mock function that logs calls to the Actions panel:
Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { ActionButton } from './Button';

const meta = {
  component: ActionButton,
} satisfies Meta<typeof ActionButton>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Basic: Story = {
  args: {
    text: 'Press me!',
    onPress: fn(),
  },
};
When you press the button in the story, the action will be logged in the Actions panel with:
  • The event name (onPress)
  • Arguments passed to the callback
  • Timestamp of the interaction

Logging Multiple Actions

Components often have multiple event handlers. You can log all of them:
Form.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { Form } from './Form';

const meta = {
  component: Form,
} satisfies Meta<typeof Form>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Interactive: Story = {
  args: {
    onSubmit: fn(),
    onCancel: fn(),
    onFieldChange: fn(),
    onValidate: fn(),
  },
};

Actions with Event Data

Actions automatically capture and display event arguments. This is useful for inspecting event payloads:
TextInput.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { TextInput } from 'react-native';

const meta = {
  component: TextInput,
} satisfies Meta<typeof TextInput>;

export default meta;

type Story = StoryObj<typeof meta>;

export const WithActions: Story = {
  args: {
    placeholder: 'Enter text...',
    onChangeText: fn(), // Logs the text value
    onFocus: fn(),       // Logs focus event
    onBlur: fn(),        // Logs blur event
  },
};
When typing in the input, each onChangeText call will appear in the Actions panel showing the new text value.

Reusing Actions Across Stories

Define actions once in the meta object to reuse across all stories:
Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { Button } from './Button';

const meta = {
  component: Button,
  args: {
    // Default action for all stories
    onPress: fn(),
  },
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

// Inherits onPress action from meta
export const Primary: Story = {
  args: {
    text: 'Primary',
    variant: 'primary',
  },
};

// Inherits onPress action from meta
export const Secondary: Story = {
  args: {
    text: 'Secondary',
    variant: 'secondary',
  },
};

// Override with custom action
export const CustomAction: Story = {
  args: {
    text: 'Custom',
    onPress: fn((event) => console.log('Custom handler', event)),
  },
};

Complete Example

Here’s a comprehensive example combining actions with other addons:
ActionButton.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { ActionButton } from './ActionButton';

const meta = {
  component: ActionButton,
  parameters: {
    notes: `
# Button Component

This is a button component with action logging.

You use it like this:

\`\`\`tsx    
<Button 
  text="Press me!" 
  onPress={() => console.log('pressed')} 
/>
\`\`\`
    `,
  },
} satisfies Meta<typeof ActionButton>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Basic: Story = {
  args: {
    text: 'Press me!',
    onPress: fn(),
  },
};

export const WithMultipleActions: Story = {
  args: {
    text: 'Long Press Me',
    onPress: fn(),
    onLongPress: fn(),
    onPressIn: fn(),
    onPressOut: fn(),
  },
};

Actions Panel

The Actions panel displays:
  • Event name: The name of the callback prop (e.g., onPress, onChange)
  • Arguments: The data passed to the callback, formatted for easy reading
  • Timestamp: When the action was triggered
  • Call count: How many times the action has been called

Panel Features

  • Clear: Remove all logged actions
  • Expand/Collapse: View detailed event data
  • Scroll: Review action history

TypeScript Support

Actions work seamlessly with TypeScript and preserve type information:
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';

interface ButtonProps {
  text: string;
  onPress: (id: string) => void;
  onLongPress?: () => void;
}

const Button = ({ text, onPress, onLongPress }: ButtonProps) => {
  // Component implementation
};

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Typed: Story = {
  args: {
    text: 'Click me',
    // TypeScript knows this should accept (id: string) => void
    onPress: fn((id: string) => console.log('Pressed', id)),
    // TypeScript knows this is optional
    onLongPress: fn(),
  },
};

Testing with Actions

Actions are particularly useful when combined with interaction testing:
Login.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-native';
import { fn } from 'storybook/test';
import { userEvent, within } from '@storybook/react-native';
import { LoginForm } from './LoginForm';

const meta = {
  component: LoginForm,
} satisfies Meta<typeof LoginForm>;

export default meta;

type Story = StoryObj<typeof meta>;

export const SubmitForm: Story = {
  args: {
    onSubmit: fn(),
    onError: fn(),
  },
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);
    
    // Simulate user interaction
    await userEvent.type(canvas.getByPlaceholderText('Email'), '[email protected]');
    await userEvent.type(canvas.getByPlaceholderText('Password'), 'password123');
    await userEvent.press(canvas.getByText('Login'));
    
    // Check that action was called
    // args.onSubmit will show in Actions panel
  },
};

Differences from Web Actions

The on-device Actions addon works similarly to the web version, with a few differences:
  • Native rendering: The Actions panel is rendered natively on your device
  • Performance: Optimized for mobile devices
  • Event objects: May differ from web events (e.g., React Native’s SyntheticEvent)
For more information about actions in web Storybook, see the official actions documentation. Most concepts apply to React Native, though the implementation is specific to mobile.

Debugging Tips

Actions Not Appearing

If actions aren’t showing up:
  1. Verify the addon is registered in .rnstorybook/main.ts
  2. Check that you’re using fn() from storybook/test
  3. Ensure the component is actually calling the callback
  4. Open the Actions panel in the Storybook UI

Clear Action History

To clear logged actions during development, use the clear button in the Actions panel.

Controls Addon

Combine with Controls to test different prop combinations

Web Actions Docs

Official web actions documentation

Build docs developers (and LLMs) love