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?
/ponents/Header/Header.stories.js
import React, { useState } from "react";
import { Header } from "./Header";
export default {
title: "Components/Header",
ponent: 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
};
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?
/ponents/Header/Header.stories.js
import React, { useState } from "react";
import { Header } from "./Header";
export default {
title: "Components/Header",
ponent: 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
};
Share
Improve this question
asked Mar 5, 2021 at 16:01
DeVoltDeVolt
3411 gold badge3 silver badges18 bronze badges
1
- storybook.js/addons/@storybook/addon-knobs ? – azium Commented Mar 5, 2021 at 16:04
3 Answers
Reset to default 8Consider approaching it from this angle instead: storybook is supposed to showcase the individual ponents, not their parent ponent's logic. See Storybook Docs:
A story captures the rendered state of a UI ponent. Developers write multiple stories per ponent that describe all the “interesting” states a ponent can support.
So instead of mocking a parent ponent and state like you've done, I remend:
- 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 ponent, and as such, we can use the useState hook to manage the input state!
https://javascript.plainenglish.io/a-guide-to-documenting-controlled-ponents-with-storybook-10b889c03f87
i follow this from official docs of storybook it works for me
import useState from 'storybook-addon-state';
import CounterComponent from '@/ponents/mon/htmlTags/counterCounter';
export default {
title: 'Html/Counter',
ponent: 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>
);
};