Storybook: Typescript typings for controls args

Created on 12 Aug 2020  路  10Comments  路  Source: storybookjs/storybook

Describe the bug

Suppose I'm using Storybook 6 with @storybook/addon-controls with React and TypeScript.

Per the medium article, basic usage of control values like so:

export const Basic = (args) => <Button {...args} />;
Basic.args = { label: 'hello button' };

Suppose the Button has these props:

interface ButtonProps {
  label?: string;
}

If I ever mutate the interface, there is no type interference for <storyName>.args.

There's value in having the component's types percolate through the system and apply to args: (1) Validating my stories conform to the component's interface, and (2) if I mutate the interface, I can easily update my stories accordingly.

In TypeScript, if I want typings, for every story in my project I must write:

interface Story {
  (): any;
  args?: ButtonProps; // Optional since args is defined after function declaration
}

export const Basic: Story = (args) => <Button {...args} />;
Basic.args = { label: 'hello button' };

To Reproduce

See above code samples.

Expected behavior

It seems verbose and overly ceremonious to have to define a new type for my stories. Is there a better way? Maybe storybook could ship an interface, so I could do something like this:

import { Story } from '@storybook/...';

export const Basic: Story<ButtonProps> = (args) => <Button {...args} />;
Basic.args = { label: 'hello button' };

where Story is

interface Story<T> {
  (): any;
  args?: T;
}
args question / support typescript

Most helpful comment

We introduced typescript types in 6.0: https://github.com/storybookjs/storybook/blob/next/docs/snippets/react/page-story.ts.mdx

We plan to enhance them in 6.1, but hopefully these address your basic needs. LMK if they don't!

All 10 comments

We introduced typescript types in 6.0: https://github.com/storybookjs/storybook/blob/next/docs/snippets/react/page-story.ts.mdx

We plan to enhance them in 6.1, but hopefully these address your basic needs. LMK if they don't!

Here are the types I'm currently using, better argTypes, hope it helps
https://gist.github.com/rabelloo/bae0397d6a331d939eacdb3e8849220f

It appears using Template.bind({}) results in type any. To have each story's args typechecked, you must manually specify the type.

Screen Shot 2020-08-19 at 9 07 36 AM

Screen Shot 2020-08-19 at 9 04 23 AM

@petermikitsh .bind({}) should return the type of the function you're binding. See TypeScript playground example.

I think the reason you're seeing this is because you haven't enabled strictBindCallApply in your TS config.

@torkelrogstad I'm running into the same problem as @petermikitsh and I've got strictBindCallApply: true in my tsconfig.json

I'd suggest to create a minimal reproduction in the TypeScript playground.

Any way to fix this?

Failed to compile.

/Users/joaopaulofricks/www/xxx-Design-System/src/stories/components/Button.stories.tsx
TypeScript error in /Users/joaopaulofricks/www/xxx-Design-System/src/stories/components/Button.stories.tsx(8,20):
Parameter 'args' implicitly has an 'any' type.  TS7006

     6 | storiesOf('Atoms/Button', module)
     7 |   .addDecorator(withKnobs)
  >  8 |   .add('Default', (args) => <Button {...args}>Default</Button>, {
       |                    ^
     9 |     component: Button,
    10 |   })
    11 |   .add(

Workaround:

8 | .add('Default', (args: any) =>

Or:

8 | .add('Default', (args: ButtonProps) =>

@shilman I have problems with any in my application. The better approach is to pass the props to the element... I'll try out, thank you :)

@jpcmf I've made a helper function in my project so that it's fully typed whilst having minimal boilerplate for each component.

helpers.tsx:

import * as React from 'react';
import { Story } from '@storybook/react';
import { StoryFnReactReturnType } from '@storybook/react/dist/client/preview/types';

export const templateForComponent = <P,>(Component: (props: P) => StoryFnReactReturnType) => (
    props: P
): Story<P> => {
    const template: Story<P> = (args) => {
        return <Component {...args} />;
    };
    const story = template.bind({});
    story.args = props;
    return story;
};

Input.stories.tsx:

import { Meta } from '@storybook/react';
import { templateForComponent } from '../helpers';
import Input from '../../ui-web-library/Form/Input';

const template = templateForComponent(Input);

const meta: Meta = {
    title: 'Form/Input',
    component: Input,
};
export default meta;

export const Currency = template({
    type: 'currency',
    fieldName: 'income',
});

It also slims down the code in comparison to using .bind() and .args for each story. Hope that helps!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

purplecones picture purplecones  路  3Comments

wahengchang picture wahengchang  路  3Comments

shilman picture shilman  路  3Comments

xogeny picture xogeny  路  3Comments

MrOrz picture MrOrz  路  3Comments