storybook: Cannot use SB hooks (`useArgs`) in React component rendered inside decorator or render function.

Describe the bug

To Reproduce Steps to reproduce the behavior:

  1. Create a sample app using CRA (I am using typescript)
  2. Run npx sb init to setup storyboard
  3. Install @storybook/api to get useArgs hook.
  4. Use the code snippet below in the Button.stories.tsx.
  5. Notice the error on Hello component.
Storybook preview hooks can only be called inside decorators and story functions.
Error: Storybook preview hooks can only be called inside decorators and story functions.
    at invalidHooksError (http://localhost:6007/vendors~main.38cdb582067630c38066.bundle.js:18317:10)
    at getHooksContextOrThrow (http://localhost:6007/vendors~main.38cdb582067630c38066.bundle.js:18328:11)
    at useStoryContext (http://localhost:6007/vendors~main.38cdb582067630c38066.bundle.js:18521:31)
    at useArgs (http://localhost:6007/vendors~main.38cdb582067630c38066.bundle.js:18549:27)
    at Object.Template (http://localhost:6007/main.86e3cb3589897769072c.hot-update.js:136:96)
    at renderWithHooks (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:2546:153)
    at mountIndeterminateComponent (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:2831:885)
    at beginWork (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:3049:101)
    at HTMLUnknownElement.callCallback (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:70:102)
    at Object.invokeGuardedCallbackDev (webpack://storybook_docs_dll//Users/shilman/projects/baseline/storybook/node_modules/react-dom/cjs/react-dom.development.js?:90:45)

Expected behavior It should work

Screenshots Screen Shot 2020-08-13 at 3 15 15 PM

Code snippets

const Template: Story<ButtonProps> = (args) => {

  // removing this works everywhere
  const [_, updateArgs] = useArgs();
  // use hook
  return <Button {...args} />;
};

// works without any issue
export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

// throws error
export const Hello = () => {
  return <Small {...(Small.args as any)} />;
};

System: Environment Info:

System: OS: macOS 10.15.5 CPU: (16) x64 Intel® Core™ i9-9980HK CPU @ 2.40GHz Binaries: Node: 13.10.1 - ~/.nvm/versions/node/v13.10.1/bin/node Yarn: 1.22.4 - /usr/local/bin/yarn npm: 6.13.7 - ~/.nvm/versions/node/v13.10.1/bin/npm Browsers: Chrome: 84.0.4147.125 Firefox: 77.0.1 Safari: 13.1.1 npmPackages: @storybook/addon-actions: ^6.0.5 => 6.0.5 @storybook/addon-essentials: ^6.0.5 => 6.0.5 @storybook/addon-links: ^6.0.5 => 6.0.5 @storybook/client-api: ^6.0.6 => 6.0.6 @storybook/node-logger: ^6.0.5 => 6.0.5 @storybook/preset-create-react-app: ^3.1.4 => 3.1.4 @storybook/react: ^6.0.5 => 6.0.5

Additional context Add any other context about the problem here.

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Comments: 45 (18 by maintainers)

Most upvoted comments

@suparngp Good sleuthing. You need a different useArgs:

import { useArgs } from '@storybook/client-api';

Is there an equivalent for Vue? I’m running into basically the same thing but I need it in vue.

@kylegach

The TLDR of the limitation is you cannot use the Storybook Hooks in React components (that are rendered by decorators or render functions). You can use the hooks in the render functions/decorators themselves of course.

So for instance if you wanted to make a complex decorator that renders a bunch of stuff and needs args, you might refactor the UI into a component and render that component in the decorator. In that case you would need to call useArgs in the decorator and pass the result into the component as prop.

Extra notes: Some people do this just purely for linting reasons, because Eslint thinks our hooks are actually React hooks, but it doesn’t think the decorator / render function is a React component. The simplest solution to that problem is simply to write the render/function in a way that looks more like a component (which it is actually):

decorators: [function Decorator(..) { }]

render: function Render() { ... }

thank you!

unfortunately the solution you suggested gives a storyblok error for me: Storybook preview hooks can only be called inside decorators and story functions.

I went with a lazy solution for now: // eslint-disable-next-line 😆

Of course after I write up a better description of the issue, I go and find a solution. The solution seems to be to switch from the @storybook/manager-api import to @storybook/preview-api. This has the necessary useArgs hook.

Here is a full working example in v7 format:

With Template Story

import { StoryObj, Meta } from "@storybook/react";
import { useArgs as UseArgs } from "@storybook/preview-api";
import { Switch, SwitchProps } from "./index";

const meta: Meta<typeof Switch> = {
    title: "Inputs/Switch",
    component: Switch,
    argTypes: {
        isDisabled: {
            control: {
                type: "boolean"
            }
        },
        isRequired: {
            control: {
                type: "boolean"
            }
        }
    }
};
export default meta;



type Story = StoryObj<typeof Switch>;

const TemplateComponent = (args: SwitchProps) => {
    const [{ isChecked }, updateArgs] = UseArgs();

    function onChange() {
        updateArgs({ isChecked: !isChecked });
    }

    return <Switch {...args} onChange={onChange} isChecked={isChecked} />;
}

const templateStory: Story = {
    render: (args) => <TemplateComponent {...args} />
}

export const Standard: Story = {
    ...templateStory,
    args: {
        isChecked: false,
        label: "Switch Me!"
    }
};

Without template story

This example doesn’t have a templateStory, but still separates the render JSX into a normal looking React component function so that it will pass eslint

import { StoryObj, Meta } from "@storybook/react";
import { useArgs } from "@storybook/preview-api";
import { Switch, SwitchProps } from ".";

const meta: Meta<typeof Switch> = {
    title: "Inputs/Switch",
    component: Switch
};
export default meta;
type Story = StoryObj<typeof Switch>;

// Separate render logic into a new function that will not cause issues with eslint rule `react-hooks/rules-of-hooks`
const WrappedComponent = (args: SwitchProps) => {
  const [{ isChecked }, updateArgs] = UseArgs();
    function onChange() {
        updateArgs({ isChecked: !isChecked });
    }
    return <Switch {...args} onChange={onChange} isChecked={isChecked} />;
}
export const Example = {    
    args: {
        isChecked: false,
        label: "Switch Me!"
    },
    render: (args) => <WrappedComponent {...args} />
};

This would be a good thing to add to the docs, or to automatically do on v7 upgrade. Just changing the import line allowed it to work without changing the rest of the stories file.

I’m facing the same issue, but on Angular.

@suparngp Thanks so much for this question as it help me resolve an issue for dynamic assign of story args 🎉

You can find detailed implementation here: https://stackoverflow.com/questions/63708208/how-to-dynamically-mutate-args-in-storybook-v6-from-the-components-action/67424836#67424836

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. Unfortunately, we don’t have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

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. Unfortunately, we don’t have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

I think I now understand the root cause of this issue.

I believe we decorate the stories lazily. So when the component which is using useArgs is wrapped in another function, only the top level function is decorated. So if I use useArgs in the wrapping function MyStory, it works because it is decorated. But the internal component Small is not and thus it throws this error. Just using Small works because it is also decorated automatically when stories are added.

That also explains why the calls happen in different order I think.

Recursively decorating the components sounds weird though, so I’d understand if this is not something that we can support. But if there is a way to create a decorated component manually, then probably my use case will be solved. For example, if I could do something like this:

const Template: Story<ButtonProps> = (args) => {
  const [_, updateArgs] = useArgs();
  return <Button {...args} />;
};

export const Small = Template.bind({});

// decorates `Small` so that it has access to the context and not exported from file to avoid double decoration.
const DecoratedSmall = decorated(Small);


export const MyStory = () => < DecoratedSmall {... DecoratedSmall.args} />

These are just guesses though because I am way to excited to migrate to v6 and trying to figure out some workaround. 😸

What’s strange about it is that my changes worked… then after a refresh in browser the error appeared. Wouldn’t have suggested to follow the code if it had made the error. It’s definitely Storybook itself which is throwing the error. I’m going to open a fresh issue for this as we’re clearly off the path of the OP’s issue.

This has been marked as “has workaround”, but what is the workaround? The problem still exists and I’m not seeing a working alternative in the thread.

If updateArgs is not intended to be used within story functions, and only within decorators, could the docs be updated? There are widespread examples of using things like an onclick or onchange to trigger an updateArgs. Which sometimes works in simple cases but not like in the example presented in the original issue where you are nesting templates.

Note: I’m also dealing with web components, and Lit. In one particular case, the error was triggering with use of the repeat directive. Using map instead was a workaround that stopped the error.

I’ve the same problem as @ArkenStorm

I’m using storybook/vue3 and the useArgs hook seems to be react-only. I hope there’s a way to (programmatically) change args for vue as well

@anthonyma94 import { useArgs } from '@storybook/client-api'; in v6 this is still supported

I’m on Storybook v6, and the client-api library is deprecated. I’m trying to use the @storybook/api library instead, but it’s throwing TypeError: Cannot read properties of undefined (reading 'getCurrentStoryData') when I use useArgs() inside of a template:

const ToggleTemplate: ComponentStory<typeof Button> = (args) => {
  const [_, updateArgs] = useArgs();

  return <Button {...args} />;
};

can anyone help me out?

If I pass updateArgs as a prop down the tree, I think it updates the args of the component where useArgs was invoked which makes sense. For now, I think I ll just wrap up the migration with the useState so that at least I am on v6. May be this is something that you might wish to consider for the next releases. I ll keep an eye.