Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass props to stories from decorators #340

Closed
isnifer opened this issue Jul 29, 2016 · 18 comments
Closed

Pass props to stories from decorators #340

isnifer opened this issue Jul 29, 2016 · 18 comments

Comments

@isnifer
Copy link

isnifer commented Jul 29, 2016

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?

@thani-sh
Copy link
Contributor

thani-sh commented Aug 6, 2016

@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.

@arunoda
Copy link
Member

arunoda commented Aug 6, 2016

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

@arunoda
Copy link
Member

arunoda commented Sep 15, 2016

I think this is not a problem anymore.

@arunoda arunoda closed this as completed Sep 15, 2016
@isnifer
Copy link
Author

isnifer commented Sep 15, 2016

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

@arunoda
Copy link
Member

arunoda commented Sep 16, 2016

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

@faceyspacey
Copy link

faceyspacey commented Mar 29, 2017

@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:

#713 (comment)

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.

@fredsmoo
Copy link

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.

@ndelangen
Copy link
Member

#1209 Will have to think about this for api-v2

@ndelangen ndelangen changed the title Context feature in decorators breaks my storybook Pass props to stories from decorators Jul 7, 2017
@stale
Copy link

stale bot commented Oct 31, 2017

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!

@stale stale bot added the inactive label Oct 31, 2017
@mspoerer
Copy link

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

@callmephilip
Copy link

callmephilip commented Nov 21, 2018

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>
));

@cortopy
Copy link
Contributor

cortopy commented Nov 28, 2018

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

@Wgil
Copy link

Wgil commented Jan 20, 2019

@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

@ndelangen
Copy link
Member

ndelangen commented Jan 21, 2019

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`] = `
  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
}
`; ```

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>);

@apabinc
Copy link

apabinc commented May 25, 2019

@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>
  ));

@ghost
Copy link

ghost commented Jul 19, 2020

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} />
  </>
);

@cybervaldez
Copy link

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.
};

@joanrodriguez
Copy link

From the docs:

parameters.passArgsFirst: In Storybook 6+, we pass the args as the first argument to the story function. The second argument is the “context” which contains things like the story parameters etc.

You should be able to access state and setState by making a small adjustment to the story function signature:

//preview.js
export const decorators = [
    Story => {
        const [state, setState] = useState();
        return <Story state={state} setState={setState} />;
    }
];

// mycomponent.stories.tsx

export const TwoButtons = (args, context) => {
  const { state, setState } = context;

  return (
    <ButtonGroup
        buttons={[
            { label: 'One',value: 'one'},
            { label: 'Two', value: 'two' }
        ]}
        selectedButton={state}
        onClick={val => setState(val)}
    />
  );
}

source: https://stackoverflow.com/questions/66731079/storybook-6-decorator-pass-props-to-story-not-working

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests