storybook: Storybook not compatible with React hooks

Describe the bug Attempting to render a react component that uses hooks into a storybook staging environment throws an error Hooks can only be called inside the body of a function component.

To Reproduce Steps to reproduce the behavior:

  1. Create a react component that uses hooks
  2. Import & render the component in storybook

Expected behavior Storybook should be able to display React components that use hooks.

Code snippets

Component code

import React, { useState } from "react";

export default function ColorChanger() {
  const [color, setColor] = useState("#000");
  const randomColor = "#" + Math.floor(Math.random() * 16777215).toString(16);
  return (
    <div style={{ color }} onClick={() => setColor(randomColor)}>
      Color is: {color} (click to change)
    </div>
  );
}

Note, this code is working on codesandbox: https://codesandbox.io/s/n5rmo77jx0

System:

  • Browser: Firefox
  • Framework: React
  • Version: 4.0.2

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 25
  • Comments: 73 (25 by maintainers)

Most upvoted comments

For my, it worked:

const MyComponent = () => {
    const [number, setNumber] = useState(0);
    return (
        <div>
            <button onClick={() => setNumber(number + 1)}>Inc</button>
            <div>{number}</div>
        </div>
    )
};

storiesOf('MyStoreOf', module)
    .add('myComponent', () => (<MyComponent /> ));

react e react-dom: 16.8.1.

Try:

import { setConfig } from 'react-hot-loader';
setConfig({ pureSFC: true });

in your .config file.

This should do the trick - for now.

So I ran into this issue using CRA 2.1.1 with typescript on the 4.1.0-alpha.8 version of @storybook/react and hooks and it ended up coming down to the fact that the package includes it’s own local copy of React so when the hook is called it can’t find the ReactSharedInternals.ReactCurrentOwner.currentDispatcher instance. As silly as it felt, I just updated my npm scripts to the following:

"storybook": "rm -rf ./node_modules/@storybook/react/node_modules/react && rm -rf ./node_modules/@storybook/react/node_modules/react-dom && start-storybook -p 9009",

And everything seemed to work. Maybe react/react-dom need to be peer-dependencies of @storybook/react instead of regular deps?

I tried the react-hot-loader setConfig thing only to find that react-hot-loader wasn’t installed in the first place. Maybe this is a change with the 4.1 alpha but I’m new to storybook in general so I wouldn’t know.

I also had this problem, and tracked it down (as someone else said before) to the fact that Storybook uses a different instance of React (one in my workspace node_modules, and one in the storybook node_modules, I’m using Yarn Workspaces, monorepo).

I tried the package.json resolution trick, to no avail.

I got it working though by tweaking the webpack.config by using aliases and forcing both react and react-dom to be used from the root node_modules:

const aliases = {
react: path.resolve(__dirname, '../../../node_modules', 'react'),
 'react-dom': path.resolve(__dirname, '../../../node_modules', 'react-dom'),
};

and then in your config :

config.resolve.alias = aliases;

Hope that helps.

import { setConfig } from 'react-hot-loader';
setConfig({ pureSFC: true });

This didn’t work for me, I get the exact same error with or without it.

I had the same problem but got it working.

  • First I had an error because my function wasn’t capitalized e.g. example => Example
  • Second I forgot how to React and tried to call the function, instead of passing it as JSX element
    • So to use ilias-t’s example, render the function component like <ColorChanger /> and not ColorChanger()
  • Lastly, after rendering my function as JSX element, it said I needed to import React in my example code from the React docs.
    • And now it works.

I did also update the react-dom package to 16.7.0-alpha.0 before I tried any of this, so perhaps, if you haven’t already, give that a go as well.

@Amolang 😄 gotcha, yeah this issue is only happening for me in when using hooks with storybook, which is why I filed the issue for storybook specifically. Will update the title to make it more clear that it’s storybook specific.

@Amolang Pretty sure I’m doing all of the same steps. The specific error message that I’m seeing leads me to believe the error isn’t a result of one of those cases.

Good thought regarding react-dom—I thought that might be it, but I updated it and still go the same error 😕

Yes alpha.2 version or 16.7.0-alpha.2.4 in terms of hot-loader/react-dom

@jgoux Yea, this is latest gotcha 😄 React 16.7.0 was released without hooks and if you are not careful, it can break apps. See more info here: https://reactjs.org/blog/2018/12/19/react-v-16-7.html#why-is-this-bugfix-a-minor-instead-of-a-patch

@theKashey I had the exact same problem as in OP and by forcing react versions it was gone. I assume it’s because Storybook uses a hook-less version of React to drive the rendering. However, when you import eg. useState, it’s a React 16.7 and it looks for the dispatcher which is most likely in some different space. I don’t know internals that much.

From all these comments this workaround makes the most sense to me. I don’t get why would hot loader be involved at all. I am on Storybook 4.1.2 and react-hot-loader is not even installed as a dependency. If some people have it in node_modules it’s most likely for different reasons so any kind of hacking webpack config can doubtfully work.

Nvm, I was using hooks inside of the .add('with ...', () => { ... return (<Component></Component>) } part which is a no go, my bad. Instead I’m creating a function outside of it and using it like .add('with ...', () => (<ComponentWithHooks></ComponentWithHooks>)) to test it with hooks.

Wrapping the story in a div worked for me

storiesOf("Hooks", module)
  .add(
    "Hook",
    () => (
      <div>
        <Hook/>
      </div>
    ),
  );

I hit the same problem using a hooks based component, and after some experimentation found the cause of it…

My story markup looked like this

<div style={{maxWidth: '280px'}}>
  <MyComponent/>
</div>

It appears that a normal <div> tag is what confuses the matter, and the solution is to make sure that the wrapping component is a React component, and not just a regular html tag. So I defined a Wrapper component like this:

const Wrapper = props => <div style={{maxWidth: '280px'}}>{props.children}</div>

and then changed my story markup to be like this

<Wrapper>
  <MyComponent>
</Wrapper>

Problem solved, and no need to import any special libraries to make it work.

@JasonTheAdams Here I describe a simple way to use hooks and still retain the jsx. It only works for child components though

https://mobile.twitter.com/HipsterSmoothie/status/1107182717467156484

My co worker built a tiny library with a hooks like API that he just released that is also jsx friendly. This is probably your best bet to get jsx and interactivity

https://github.com/adierkens/storybook-addon-state

I guess this should be closed then @ilias-t

This should work with the 4.1 release. According to the release notes, Storybook now uses the React versions from the project for the actual preview (and whatever it uses internally for the manager).

That means, if you update Storybook to 4.1 or later, and React + React-DOM to 16.8.0-alpha.1, you should have Hooks working in Storybook.

The fix I’ve finally found is to force a specific version of @hot-loader/react-dom during the installation of the patch.

My current version of react-dom is 16.7.0-alpha.2, so I just did:

yarn add @hot-loader/react-dom@16.7.0-alpha.2

and my .storybook/webpack.config.js is:

module.exports = (baseConfig, env, defaultConfig) => {

  // you can add more configs here...

  defaultConfig.resolve.alias['react-dom'] = '@hot-loader/react-dom';
  return defaultConfig;
};

And now hooks are working fine inside storybook!

React-Hot-Loader 4.6.0 should work with hooks out of the box. https://medium.com/@antonkorzunov/react-hot-loader-4-6-41f3ce76fb08