Mock useState in StorybookJS

Viewed 12636

I wonder, what is the "Best Practice" for mocking React States in Storybooks (e.g. *.stories.js).

Currently I'm trying to implement a Dark Theme Switch.

  1. App Component has a state called "darkState", which can be set true/false
  2. App Component has a handler "handleThemeChange()", which changes MUI Theme, based upon "darkState"
  3. Header Component has a Switch or Button with "onChange()" which triggers "handleThemeChange()" in App Component
  4. The MUI Switch needs a state in order to work properly (at least I guess it does)

So, I decided to mock the state in my stories file. But writing this in a decorators seems ... strange. How do you solve this problem?

/components/Header/Header.stories.js

import React, { useState } from "react";
import { Header } from "./Header";

export default {
  title: "Components/Header",
  component: Header,
  decorators: [
    (StoryFn) => {
      // mock state
      const [darkState, setDarkState] = useState(false);
      const handleThemeChange = () => {
        setDarkState(!darkState);
        return darkState;
      };

      return (
        <Header
          enableThemeChange={true}
          handleThemeChange={handleThemeChange}
          darkState={darkState}
        />
      );
    }
  ]
};

const Template = (args) => <Header {...args} />;
export const Default = Template.bind({});

// define Controls
Default.args = {
  enableThemeChange: true,
  darkState: true
};
3 Answers

I think you are approaching this from a wrong angle, storybook is supposed to showcase the individual components, not their parent component's logic. See Storybook Docs:

A story captures the rendered state of a UI component. Developers write multiple stories per component that describe all the “interesting” states a component can support.

So instead of mocking a parent component and state like you've done, I recommend:

  1. Create multiple stories to capture the different states (dark vs light)
  2. use the Actions addon for the handler functions, so you still receive feedback when the function is invoked.

i follow this from official docs of storybook it works for me

    import useState from 'storybook-addon-state';
    import CounterComponent from '@/components/common/htmlTags/counterCounter';
    
        export default {
          title: 'Html/Counter',
          component: CounterComponent,
          args: {
            children: 'Counter',
          },
        };
        
        export const NumberCounter = () => {
          const [numberOfCount, SetNumberOfCount] = useState('clicks', 1);
          return (
            <CounterComponent className="booking-item-group d-flex">
              <li className={`item ${numberOfCount === 1 && `active`}`}>
                <AnchorComponent
                  onClick={() => {
                    SetNumberOfCount(1);
                  }}
                  className="link"
                >
                  1
                </AnchorComponent>
              </li>
              <li className={`item ${numberOfCount === 2 && `active`}`}>
                <AnchorComponent
                  onClick={() => {
                    SetNumberOfCount(2);
                  }}
                  className="link"
                >
                  2
                </AnchorComponent>
              </li>
              <li className={`item ${numberOfCount === 3 && `active`}`}>
                <AnchorComponent
                  onClick={() => {
                    SetNumberOfCount(3);
                  }}
                  className="link"
                >
                  3
                </AnchorComponent>
              </li>
              <li className={`item ${numberOfCount === 4 && `active`}`}>
                <AnchorComponent
                  onClick={() => {
                    SetNumberOfCount(4);
                  }}
                  className="link"
                >
                  4
                </AnchorComponent>
              </li>
              <li className={`item ${numberOfCount === 5 && `active`}`}>
                <AnchorComponent
                  onClick={() => {
                    SetNumberOfCount(5);
                  }}
                  className="link"
                >
                  5
                </AnchorComponent>
              </li>
            </CounterComponent>
          );
        };
Related