Storybook: Can't use hooks in Storyshots

Created on 28 Mar 2019  ·  7Comments  ·  Source: storybookjs/storybook

Describe the bug

Hello, I'm trying to add Storyshots to a project on [email protected] (latest) and @storybook/*@5.0.5 (also latest) that uses hooks.

Storyshots runs fine for the majority of components / stories. But fails on any component that uses hooks:

● Storyshots › TEST|HooksTest › default

    Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
    1. You might have mismatching versions of React and the renderer (such as React DOM)
    2. You might be breaking the Rules of Hooks
    3. You might have more than one copy of React in the same app
    See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.

      2 |
      3 | export const HooksTest = () => {
    > 4 |   const [count, setCount] = useState(0);
        |                             ^
      5 |   return (
      6 |     <div>
      7 |       <p>Count is {count}</p>

      at invariant (node_modules/react/cjs/react.development.js:88:15)
      at resolveDispatcher (node_modules/react/cjs/react.development.js:1436:28)
      at useState (node_modules/react/cjs/react.development.js:1461:20)
      at HooksTest (src/lib/storybook/__snapshots__/HooksText.component.js:4:29)
      at mountIndeterminateComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4137:15)
      at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4541:16)
      at performUnitOfWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7377:16)
      at workLoop (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7406:26)
      at renderRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7437:9)
      at performWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8012:24)
      at performWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7933:9)
      at performSyncWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7910:5)
      at requestWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7810:7)
      at scheduleWorkImpl (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7685:13)
      at scheduleWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7645:12)
      at scheduleRootUpdate (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8273:5)
      at updateContainerAtExpirationTime (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8301:12)
      at Object.updateContainer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8328:14)
      at create (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9009:18)
      at getRenderedTree (node_modules/@storybook/addon-storyshots/dist/frameworks/react/renderTree.js:22:16)
      at node_modules/@storybook/addon-storyshots/dist/test-bodies.js:21:18
      at node_modules/@storybook/addon-storyshots/dist/test-bodies.js:49:35
      at Object.<anonymous> (node_modules/@storybook/addon-storyshots/dist/api/snapshotsTestsTemplate.js:35:33)

This is my super-basic test component:

import React, { useState } from 'react';

export const HooksTest = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count is {count}</p>
      <button onClick={() => setCount(count + 1)}>Set count</button>
    </div>
  );
};

and this is the story that runs it:

import React from 'react';
import { storiesOf } from '@storybook/react';
import { HooksTest } from './HooksText.component';

storiesOf('TEST|HooksTest', module).add('default', () => <HooksTest />);

And the storyshot.test.js

import initStoryshots, {
  multiSnapshotWithOptions,
  Stories2SnapsConverter,
} from '@storybook/addon-storyshots';
import addons, { mockChannel } from '@storybook/addons';

addons.setChannel(mockChannel());

// In storybook webpack, semantic CSS is loaded with `style-loader/useable`
// which is blowing up in CI. This is a basic mock for the usable api.

initStoryshots({
  // storyKindRegex: /^((?!.*?SKIPSNAP).)*$/, // add "SKIPSNAP" to a `storiesOf` to skip a whole group of stories in storyshot snapshot tests
  storyKindRegex: /TEST\|HooksTest/, // add "SKIPSNAP" to a `storiesOf` to skip a whole group of stories in storyshot snapshot tests
  storyNameRegex: /^((?!.*?SKIPSNAP).)*$/, // add "SKIPSNAP" to a story `.add` to skip a single story in storyshot snapshot tests
  test: multiSnapshotWithOptions({
    createNodeMock: element =>
      element.type && document.createElement(element.type),
  }),
  stories2snapsConverter: new Stories2SnapsConverter({
    snapshotExtension: '.js.snap',
  }),
});

Additional context

I do have several addons installed and configured and am using a custom Webpack. I'm not sure what might be related though. I can provide more detail if that helps.

Other addons

    "@storybook/addon-a11y": "^5.0.5",
    "@storybook/addon-actions": "^5.0.5",
    "@storybook/addon-info": "^5.0.5",
    "@storybook/addon-knobs": "^5.0.5",
    "@storybook/addon-links": "^5.0.5",
    "@storybook/addon-storyshots": "5.0.5",
    "@storybook/addons": "^5.0.5",
    "@storybook/react": "^5.0.5",
    "storybook-addon-intl" "2.4.0",
    ""

.storybook/config

import React from 'react';
import requireContext from 'require-context.macro';
import { configure, addDecorator, addParameters } from '@storybook/react';
import { withA11y } from '@storybook/addon-a11y';
import { setIntlConfig, withIntl } from 'storybook-addon-intl';
import { addLocaleData } from 'react-intl';
import enLocaleData from 'react-intl/locale-data/en';
import frLocaleData from 'react-intl/locale-data/fr';
import messages, { DEFAULT_LOCALE } from 'common/locales';
import StoryRouter from 'storybook-react-router';
import { ThemeProvider } from 'styled-components';
import { theme } from 'ui-kit';
import { env, ENV } from 'mocktail';

// this allows mocking of components in Storybook using Mocktail (https://github.com/Wildhoney/Mocktail)
// See src/apps/enrollment/ui-kit/mockable-redux-first-router-link/README.md
env(ENV.TESTING);

const req = requireContext('../src', true, /\.stories.js$/);

function loadStories() {
  req.keys().forEach(filename => req(filename));
}

addDecorator(StoryRouter());

addDecorator(withA11y);

addDecorator(story => <ThemeProvider theme={theme}>{story()}</ThemeProvider>);

const setupIntl = () => {
  addLocaleData(enLocaleData);
  addLocaleData(frLocaleData);

  const getMessages = locale => messages[locale.split('-').shift()];

  setIntlConfig({
    locales: ['en-CA', 'fr-CA', 'en-US', 'keys'],
    defaultLocale: DEFAULT_LOCALE,
    getMessages,
  });

  addDecorator(story => withIntl(() => story()));
};
setupIntl();

addParameters({
  options: {
    name: 'League',
  },
});

configure(loadStories, module);

.storybook/webpack.config.js

/***
 * Use style-loader/usable to lazy load / unload Semantic CSS only for stories that need it.
 * Works with `./src/lib/storybook/with-semantic-css`.
 *
 * See https://github.com/webpack-contrib/style-loader#useable
 *
 * All other css is loaded in the normal way.
 */
const useLazyLoadingForSemanticCSS = config => {
  const { module, module: { rules = [] } } = config;
  const isCssRule = ({ test }) => test.toString() === /\.css$/.toString();
  const cssRule = rules.find(isCssRule);
  const cssRuleWithSemanticExcluded = {
    ...cssRule,
    exclude: [/\.module\.css$/, /semantic\.css$/],
  };
  const lazySemanticCssRule = {
    test: /semantic\.css$/,
    use: ['style-loader/useable', ...cssRule.use.slice(1)],
  };

  return {
    ...config,
    module: {
      ...module,
      rules: [
        ...rules.filter(rule => !isCssRule(rule)),
        cssRuleWithSemanticExcluded,
        lazySemanticCssRule,
      ],
    },
  };
};

module.exports = ({ config }) => useLazyLoadingForSemanticCSS(config);

Note the story I'm testing does not use semantic.css so style-loader/useable is not used!

storyshots react bug cra has workaround

Most helpful comment

UPDATE:

I have figured out that my version of react_test_renderer was outdated. It was at 16.3.2. Running

yarn upgrade react-test-renderer --latest

upgraded me to [email protected]. This is the same version number as react, react-dom etc!

So it seems the error was due to the version mismatch between react-dom and react-test-renderer.

All 7 comments

UPDATE:

I have figured out that my version of react_test_renderer was outdated. It was at 16.3.2. Running

yarn upgrade react-test-renderer --latest

upgraded me to [email protected]. This is the same version number as react, react-dom etc!

So it seems the error was due to the version mismatch between react-dom and react-test-renderer.

Thanks for posting this @theinterned! Not sure what's with people giving 👎, this saved me a bunch of time.

@jerridan Apparently people hate helpful workarounds 🤷‍♂

Hi! I'm facing a similar problem. This is what my package looks like

"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-test-renderer": "^16.13.1",

Is this bug currently being looked at? 😕

I have same issue with

"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-test-renderer": "^16.13.1",
"@storybook/react": "^5.3.18",

Same for me ^

Released https://github.com/storybookjs/storybook/releases/tag/v6.0.0-alpha.43 containing #10529 which may fix this issue

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shilman picture shilman  ·  3Comments

dnlsandiego picture dnlsandiego  ·  3Comments

purplecones picture purplecones  ·  3Comments

wahengchang picture wahengchang  ·  3Comments

arunoda picture arunoda  ·  3Comments