storybook: Storybook templates do not handle default prop values correctly if they are objects

Describe the bug Storybook templates do not appear to handle props default values correctly if they are complex objects. The default value using the template bind is yielding the expression as a string instead of evaluating it.

To Reproduce/Code snippets

Disclaimer: i really dumbed down the example, so i haven’t checked for typos and so on… just to illustrate the problem

Component example

import { Option } from "prelude-ts";
import React from "react";

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  customPlaceholder?: Option<string>;
}

const Input: React.FC<InputProps> = ({
  customPlaceholder = Option.none(),
  ...otherProps
}) => (
  <input placeholder={customPlaceholder.getOrUndefined()} {...otherProps} />
);
export default Input;

And the following story:

type InputStoryComponentProps = React.ComponentProps<typeof Input> & {
  label: string;
};

const InputTemplate: InputStoryComponentProps = ({ label, ...otherArgs }) => (
  <div>
    <span>{label}</span>
    <Input {...otherArgs} />
  </div>
);

export const DefaultInput = InputTemplate.bind({});
DefaultInput.storyName = "Default input";

Opening the story in storybook i get the following: customPlaceholder.getOrUndefined is not a function

Debugging it, i discovered that using the template, the props is being initialized with “Option.none()” instead of the actual evaluated Option.none(), which of course yield the previous error.

If i don’t use the template, and just write a story, it works perfectly fine (of course i end up duplicating alot of code between the real case complex stories which was the main point of using the template).

Expected behavior Default prop values are properly initialised as evaluated and not passed as a string

System:

Environment Info:

  Binaries:
    Node: 14.7.0 - ~/.nvm/versions/node/v14.7.0/bin/node
    npm: 6.14.7 - ~/.nvm/versions/node/v14.7.0/bin/npm
  Browsers:
    Chrome: 85.0.4183.102
    Firefox: 80.0.1
    Safari: 13.1.2
  npmPackages:
    @storybook/addon-actions: ^6.0.21 => 6.0.21
    @storybook/addon-docs: ^6.0.21 => 6.0.21
    @storybook/addon-knobs: ^6.0.21 => 6.0.21
    @storybook/addon-links: ^6.0.21 => 6.0.21
    @storybook/addons: ^6.0.21 => 6.0.21
    @storybook/preset-create-react-app: ^3.1.4 => 3.1.4
    @storybook/react: ^6.0.21 => 6.0.21

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 8
  • Comments: 17 (3 by maintainers)

Most upvoted comments

A workaround that I’ve used that I think is elegant is to apply the component’s default props as default args at the component level, e.g.:

export default {
    title: 'Component'
    component: Component,
    // set default args for all stories
    args: { ...Component.defaultProps },
}

This correctly applies functions and objects as props. Remember to configure your JSX addon (see the react-element-to-jsx-string part) to not show default props in the code snippets 🙂

Hey folks 😃

Just wanna check if it’s going to be solved any time soon? It’s not that obvious issue and I guess it becomes really frustrating for lots of people as they don’t even have an idea what’s broken and how to google about it 😃

I would just fill the args with a deep copy of defaultProps, and modify properties on separate lines. This should work everywhere and without side-effects because defaultProps have to be able to be JSON stringified.


const story = {
    title: 'Component',
    component: Component,
    args: JSON.parse(JSON.stringify(Component.defaultProps))
}
story.args.someMuiProp.label = "some label"
story.args.anotherMuiProp.foo = "bar"
export default story

If you had lodash and were so inclined, you could also create a function which hid some messiness, allowing you to write your stories like you wanted to in the first place:

function withDefaultProps(story) {
    defaultCopy = JSON.parse(JSON.stringify(story.component.defaultProps))
    story.args = _.merge(defaultCopy, story.args)
}

export default withDefaultProps({
    title: 'Component',
    component: Component,
    args: {
        someMuiProp: {
            label: "some label"
        },
        anotherMuiProp: {
            foo: "bar"
        },
    },
})

It would be nice to mention this somewhere. I didn’t know if I am missing something in my configuration.