Controlling global decorators via Storybook `args` and `parameters`
Mike Schutte
Posted on May 27, 2021
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];
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 })
}
}
}
}
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:
- Use a good ol' pure callback function in the
args
config that takes theargs
as a value and returns a partial of theargs
object. - Check for that callback function in the global decorator
- If the callback is there, invoke it and assign the result to the
args
object (or whatever part needs mutation). - Pass around your updated data accordingly.
Enjoy!
Posted on May 27, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.