Storybook: Pass props to stories from decorators

Created on 29 Jul 2016  路  17Comments  路  Source: storybookjs/storybook

Hi, guys!

I have updated storybook from 1.17.0 to 1.41.0 today.
I found that some of my components doesn't work.

Early (in 1.17.0) one piece of code was https://github.com/kadirahq/react-storybook/blob/a6a6458385a58611984e8db8bd96ced7038661c5/src/client/client_api.js#L24

And it was beautiful, because I did something like this:

import React, { Component } from 'react';
import { storiesOf, action } from '@kadira/storybook';

class RadioWrapper extends Component {
    state = {
        value: '1',
    }

    onChange = ({currentTarget: {value}}) => {
        action('change radio')(value);
        this.setState({value});
    }

    render() {
        return (
            <div style={{width: '500px', padding: '20px'}}>
                {this.props.child({
                    value: this.state.value,
                    onChange: this.onChange,
                })}
            </div>
        );
    }
}

storiesOf('common.radio', module)
    .addDecorator(getRadio => (
        <RadioWrapper child={getRadio} />
    ))
    .add('Default', ({value, onChange}) => (
        <div>
            <div className="radio">
                <input
                    type="radio"
                    id="radio"
                    name="radio"
                    value="1"
                    checked={value === '1'}
                    className="radio__input"
                    onChange={onChange}
                />
                <label className="radio__label" htmlFor="radio">
                    Radio label 1
                </label>
            </div>
            <div className="radio">
                <input
                    type="radio"
                    id="radio_2"
                    name="radio"
                    value="2"
                    checked={value === '2'}
                    className="radio__input"
                    onChange={onChange}
                />
                <label className="radio__label" htmlFor="radio_2">
                    Radio label 2
                </label>
            </div>
        </div>
    ))
    .add('Disabled', ({value, onChange}) => (
        <div>
            <div className="radio">
                <input
                    type="radio"
                    id="radio"
                    value="1"
                    checked={value === '1'}
                    className="radio__input"
                    onChange={onChange}
                    disabled
                />
                <label className="radio__label" htmlFor="radio">
                    Radio label 1
                </label>
            </div>
            <div className="radio">
                <input
                    type="radio"
                    id="radio_2"
                    value="2"
                    checked={value === '2'}
                    className="radio__input"
                    onChange={onChange}
                    disabled
                />
                <label className="radio__label" htmlFor="radio_2">
                    Radio label 2
                </label>
            </div>
        </div>
    ));

Currently this piece of code was changed.
https://github.com/kadirahq/react-storybook/blob/master/src/client/preview/client_api.js#L60
I can't do this anymore.
But I want to pull down props into examples from Decorators.
How can I do that now?

addons stories

Most helpful comment

In V5 this is supported:

import React from 'react';
import { storiesOf } from '@storybook/react';

storiesOf('Core|Parameters/Decorator aguments', module)
  .addDecorator(fn => fn({ decoratorArgument: true }))
  .add('to Storybook', data => <pre>Parameters are {JSON.stringify(data, null, 2)}</pre>);

Here's the full storyshot of this story:

exports[`Storyshots Core|Parameters/Decorator aguments to Storybook 1`] = `
<pre>
  Parameters are {
  "kind": "Core|Parameters/Decorator aguments",
  "name": "to Storybook",
  "parameters": {
    "fileName": "/Users/dev/Projects/GitHub/storybook/core/examples/official-storybook/stories/core/decorator-agument.stories.js",
    "options": {
      "hierarchyRootSeparator": "|",
      "hierarchySeparator": {},
      "name": "Storybook"
    },
    "a11y": {},
    "viewports": {
      "iphone5": {
        "name": "iPhone 5",
        "styles": {
          "height": "568px",
          "width": "320px"
        },
        "type": "mobile"
      },
      "iphone6": {
        "name": "iPhone 6",
        "styles": {
          "height": "667px",
          "width": "375px"
        },
        "type": "mobile"
      },
      "iphone6p": {
        "name": "iPhone 6 Plus",
        "styles": {
          "height": "736px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "iphone8p": {
        "name": "iPhone 8 Plus",
        "styles": {
          "height": "736px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "iphonex": {
        "name": "iPhone X",
        "styles": {
          "height": "812px",
          "width": "375px"
        },
        "type": "mobile"
      },
      "iphonexr": {
        "name": "iPhone XR",
        "styles": {
          "height": "896px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "iphonexsmax": {
        "name": "iPhone Xs Max",
        "styles": {
          "height": "896px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "ipad": {
        "name": "iPad",
        "styles": {
          "height": "1024px",
          "width": "768px"
        },
        "type": "tablet"
      },
      "ipad10p": {
        "name": "iPad Pro 10.5-in",
        "styles": {
          "height": "1112px",
          "width": "834px"
        },
        "type": "tablet"
      },
      "ipad12p": {
        "name": "iPad Pro 12.9-in",
        "styles": {
          "height": "1366px",
          "width": "1024px"
        },
        "type": "tablet"
      },
      "galaxys5": {
        "name": "Galaxy S5",
        "styles": {
          "height": "640px",
          "width": "360px"
        },
        "type": "mobile"
      },
      "galaxys9": {
        "name": "Galaxy S9",
        "styles": {
          "height": "1480px",
          "width": "720px"
        },
        "type": "mobile"
      },
      "nexus5x": {
        "name": "Nexus 5X",
        "styles": {
          "height": "660px",
          "width": "412px"
        },
        "type": "mobile"
      },
      "nexus6p": {
        "name": "Nexus 6P",
        "styles": {
          "height": "732px",
          "width": "412px"
        },
        "type": "mobile"
      },
      "pixel": {
        "name": "Pixel",
        "styles": {
          "height": "960px",
          "width": "540px"
        },
        "type": "mobile"
      },
      "pixelxl": {
        "name": "Pixel XL",
        "styles": {
          "height": "1280px",
          "width": "720px"
        },
        "type": "mobile"
      },
      "kindleFire2": {
        "name": "Kindle Fire 2",
        "styles": {
          "width": "600px",
          "height": "963px"
        },
        "type": "tablet"
      },
      "kindleFireHD": {
        "name": "Kindle Fire HD",
        "styles": {
          "width": "533px",
          "height": "801px"
        },
        "type": "tablet"
      }
    }
  },
  "id": "core-parameters-decorator-aguments--to-storybook",
  "options": {},
  "decoratorArgument": true
}
</pre>
`;


Here's a somewhat realisting example:

import React from 'react';
import { storiesOf } from '@storybook/react';

storiesOf('Path|to/Component', module)
  .addDecorator(fn => fn({ myData: 42 }))
  .add('variant', ({ myData }) => <pre>{myData}</pre>);

All 17 comments

@arunoda with the new addon API I guess we can get rid of the context argument too. The context argument is not documented and as far as I know only the story info addon is using it.

@mnmtanish Yeah! I think we no longer need it.
BTW: Try to help on this case.

I think this is not a problem anymore.

@arunoda it's not a problem for you, but what a solution for now? This piece of code wasn't changed.

Okay. I think we can do something about this.
Reopening this.

@arunoda @mnmtanish perhaps when the context argument is addressed, being able to use promises (for the return of add) as described here can be incorporated:

https://github.com/storybooks/react-storybook/issues/713#issuecomment-290016893

I'd be happy to make a PR. Please let me know if I'm on the right track and if you see anything I'm missing.

I am currently facing the same problem as isnifer. What is the recommended way of making inputs with value and onChange props work in Storybook? Decorating them with a stateful wrapper component was a great solution but apparently there is no direct access to children and their props from the decorator anymore.

1209 Will have to think about this for api-v2

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. We do try to do some housekeeping every once in a while so inactive issues will get closed after 90 days. Thanks!

common guys... I mean seriously... a simple question and no answer at all. Not even a hint.

I have ended up doing smth like this to globally inject a translation mock for i18n (all the components need access to the t func providing translation for a given key):

class TranslationProvider extends Component {
  render() {
    const { children } = this.props;
    return React.cloneElement(children, {
      t: key => key
    });
  }
}

addDecorator(story => (
  <TranslationProvider>{story()}</TranslationProvider>
));

@ndelangen would you able to reopen issue? question is still unanswered and there has been interest on it

@isnifer example is the most intuitive way to do this.

A workaround I ended up doing:

const Decorator = ({ story, ...props }) => (
  <div style={{ textAlign: 'center' }}>
    {React.cloneElement(story(), { ...props })}
  </div>
)
addDecorator(story => <Decorator story={story} />)

Hope this helps

In V5 this is supported:

import React from 'react';
import { storiesOf } from '@storybook/react';

storiesOf('Core|Parameters/Decorator aguments', module)
  .addDecorator(fn => fn({ decoratorArgument: true }))
  .add('to Storybook', data => <pre>Parameters are {JSON.stringify(data, null, 2)}</pre>);

Here's the full storyshot of this story:

exports[`Storyshots Core|Parameters/Decorator aguments to Storybook 1`] = `
<pre>
  Parameters are {
  "kind": "Core|Parameters/Decorator aguments",
  "name": "to Storybook",
  "parameters": {
    "fileName": "/Users/dev/Projects/GitHub/storybook/core/examples/official-storybook/stories/core/decorator-agument.stories.js",
    "options": {
      "hierarchyRootSeparator": "|",
      "hierarchySeparator": {},
      "name": "Storybook"
    },
    "a11y": {},
    "viewports": {
      "iphone5": {
        "name": "iPhone 5",
        "styles": {
          "height": "568px",
          "width": "320px"
        },
        "type": "mobile"
      },
      "iphone6": {
        "name": "iPhone 6",
        "styles": {
          "height": "667px",
          "width": "375px"
        },
        "type": "mobile"
      },
      "iphone6p": {
        "name": "iPhone 6 Plus",
        "styles": {
          "height": "736px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "iphone8p": {
        "name": "iPhone 8 Plus",
        "styles": {
          "height": "736px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "iphonex": {
        "name": "iPhone X",
        "styles": {
          "height": "812px",
          "width": "375px"
        },
        "type": "mobile"
      },
      "iphonexr": {
        "name": "iPhone XR",
        "styles": {
          "height": "896px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "iphonexsmax": {
        "name": "iPhone Xs Max",
        "styles": {
          "height": "896px",
          "width": "414px"
        },
        "type": "mobile"
      },
      "ipad": {
        "name": "iPad",
        "styles": {
          "height": "1024px",
          "width": "768px"
        },
        "type": "tablet"
      },
      "ipad10p": {
        "name": "iPad Pro 10.5-in",
        "styles": {
          "height": "1112px",
          "width": "834px"
        },
        "type": "tablet"
      },
      "ipad12p": {
        "name": "iPad Pro 12.9-in",
        "styles": {
          "height": "1366px",
          "width": "1024px"
        },
        "type": "tablet"
      },
      "galaxys5": {
        "name": "Galaxy S5",
        "styles": {
          "height": "640px",
          "width": "360px"
        },
        "type": "mobile"
      },
      "galaxys9": {
        "name": "Galaxy S9",
        "styles": {
          "height": "1480px",
          "width": "720px"
        },
        "type": "mobile"
      },
      "nexus5x": {
        "name": "Nexus 5X",
        "styles": {
          "height": "660px",
          "width": "412px"
        },
        "type": "mobile"
      },
      "nexus6p": {
        "name": "Nexus 6P",
        "styles": {
          "height": "732px",
          "width": "412px"
        },
        "type": "mobile"
      },
      "pixel": {
        "name": "Pixel",
        "styles": {
          "height": "960px",
          "width": "540px"
        },
        "type": "mobile"
      },
      "pixelxl": {
        "name": "Pixel XL",
        "styles": {
          "height": "1280px",
          "width": "720px"
        },
        "type": "mobile"
      },
      "kindleFire2": {
        "name": "Kindle Fire 2",
        "styles": {
          "width": "600px",
          "height": "963px"
        },
        "type": "tablet"
      },
      "kindleFireHD": {
        "name": "Kindle Fire HD",
        "styles": {
          "width": "533px",
          "height": "801px"
        },
        "type": "tablet"
      }
    }
  },
  "id": "core-parameters-decorator-aguments--to-storybook",
  "options": {},
  "decoratorArgument": true
}
</pre>
`;


Here's a somewhat realisting example:

import React from 'react';
import { storiesOf } from '@storybook/react';

storiesOf('Path|to/Component', module)
  .addDecorator(fn => fn({ myData: 42 }))
  .add('variant', ({ myData }) => <pre>{myData}</pre>);

@Wgil You saved my life

Anyone looking for a solution for redux-form

const RootProvider = ({ children }) => (
  <Provider store={store}>
    {children}
  </Provider>
);

// ...props are from the reduxForm wrapper
const Form = ({ children, ...props }) => React.cloneElement(children, { ...props });

const ReduxForm = reduxForm({ form: 'storybook' })(Form);

storiesOf('Inline form', module)
  .add('Multiline input form', () => (
    <RootProvider>
      <ReduxForm>
        {/* component with redux-form components inside, e.g. <FieldArray /> etc */}
        <InlineForm columnData={columnData} dropdownValues={dropdownValues} />
      </ReduxForm>
    </RootProvider>
  ));

This works similar in the new CSF format as well

export default {
  title: 'MyComponent',
  component: MyComponent,
  decorators: [
    story => (
      <Provider store={store}>
          <Router>
            <div className="m24" style={{ minHeight: '620px ' }}>
              { story({ someProp: 34}) }
            </div>
          </Router>
      </Provider>
    ),
  ],
};

and can be used as:

export const defaultStory = ({ someProp }) => (
  <>
    <MyComponent {...defaultProps} someProp={someProp} />
  </>
);

The second parameter of a decorator is also the story's properties. I'm using this decorator so I would not be repeat adding labels to my buttons, etc.

export default {
  // ...
  decorators: [
    (story, props) => story({ args: { label: props.story, ...props.args } }),
  ],
}

export const ActiveButton = Wrapper.bind({}); // label will be it's export key (ActiveButton)

export const DisabledButton= Wrapper.bind({});
DisabledButton.args = {
  label: "[DISABLED]" // manually set the label.
};

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sakulstra picture sakulstra  路  3Comments

purplecones picture purplecones  路  3Comments

rpersaud picture rpersaud  路  3Comments

levithomason picture levithomason  路  3Comments

ZigGreen picture ZigGreen  路  3Comments