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.
- App Component has a state called "darkState", which can be set true/false
- App Component has a handler "handleThemeChange()", which changes MUI Theme, based upon "darkState"
- Header Component has a Switch or Button with "onChange()" which triggers "handleThemeChange()" in App Component
- 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 }; 13 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:
- Create multiple stories to capture the different states (dark vs light)
- use the Actions addon for the handler functions, so you still receive feedback when the function is invoked.
Decorator does seem strange when used like this.
If you take a closer look, the template is itself a React functional component, and as such, we can use the useState hook to manage the input state!
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> ); }; 1