Controlling global decorators via Storybook `args` and `parameters`

tmikeschu

Mike Schutte

Posted on May 27, 2021

Controlling global decorators via Storybook `args` and `parameters`

Storybook decorators (in React) provide a powerful way to reuse component environments across multiple stories. However, there aren't any off-the-shelf levers to manipulate global decorators from individual stories.

At work (we're hiring!) I recently cleaned up our many context providers into one Base decorator. Here is a simplified example.

// .storybook/decorators/base.tsx
export const Base: DecoratorFn = (Story, options) => {
  return (
    <TestReactRoot {...options.args}>
      <Story {...options} />
    </TestReactRoot>
  );
};

// .storybook/preview.js
import { Base } from './decorators/base';

export const decorators = [Base];
Enter fullscreen mode Exit fullscreen mode

TestReactRoot encapsulates a few providers, including the classic react-redux provider. So now we can easily write stories that have useSelector and other Redux hooks with minimal boilerplate. But how do I, say, set the initial Redux state from a story, when there is no visible reference to the global Base decorator? Specifically, I want to use Storybook controls to dynamically set the Redux state.

I couldn't find any existing strategies for this in the Storybook community, so I ended up using inversion of control: individual stories supply a function to the args config, which the global decorator invokes.

// ./storybook/decorators/base.tsx
export const Base: DecoratorFn = (Story, options) => {
  const { args, parameters } = options;

  if (parameters.modifyArgs) {
    Object.assign(args, parameters.modifyArgs(args));
  }

  return (
    <TestReactRoot {...args}>
      <Story {...options} />
    </TestReactRoot>
  );
};

// src/components/user-avatar.stories.tsx
export default {
  title: "User Avatar",
  args: {
    admin: false,
  },
  parameters: {
    modifyArgs: (args) => {
      return {
        reduxState: generateReduxState({ admin: args.admin })
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Boom! The story config just knows it can pass a pure function to modifyArgs, and the Base decorator decides what do do with the return value.

So there you have it: if you want to influence global decorator/provider state via Storybook controls:

  1. Use a good ol' pure callback function in the args config that takes the args as a value and returns a partial of the args object.
  2. Check for that callback function in the global decorator
  3. If the callback is there, invoke it and assign the result to the args object (or whatever part needs mutation).
  4. Pass around your updated data accordingly.

Enjoy!

💖 💪 🙅 🚩
tmikeschu
Mike Schutte

Posted on May 27, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related