Does Storybook have an opinion or mechanism for providing state to controlled components? #21680
Replies: 7 comments 10 replies
-
|
Hi @dwjohnston sorry we could not answer your interesting question we were busy with the V7 RC. So the answer for me is you newWayAttempt2 seems just perfect. |
Beta Was this translation helpful? Give feedback.
-
yes storybook control can't be function as it will be issue with code injection which is not safe, however args can be functions that return a values type string , number , json , string[] ,number[] ... |
Beta Was this translation helpful? Give feedback.
-
|
Would it make sense to treat args as state? I mean essentially args are state, you can edit it directly and it update the component right away. My use case is for a RadioGroup. If we could use the onChange handler to modify the args then it wouldn't require the addition of state. I know it works fine adding state but the automated documentation ends up being different than it should be used since you have to add an intermediate component that holds the state. I'm sure it's not trivial to add something like this but wondering if it even makes sense based on the Storybook paradigm. |
Beta Was this translation helpful? Give feedback.
-
You can add custom action if you add import { action } from '@storybook/addon-actions'
...
const onChange = (value) => {
action('onChange')(value) // or action('onChange: ' + value)()
setValue(value)
}Unfortunately, it is undocumented AFAIK. I find it is very confusing that we have |
Beta Was this translation helpful? Give feedback.
-
|
I think I fixed all the problems and could connect controlled component to Controls. See here: https://stackblitz.com/edit/github-uixpen-p6osgl?file=src%2Fstories%2FInputField.js,src%2Fstories%2FInputField.stories.js To make code cleaner I added const useControlledProps = (args) => {
const [, setArgs] = useArgs()
const [value, setValue] = useState(args.value || null)
useEffect(() => {
action('onChange')(args.value)
setValue(args.value)
}, [args.value])
const onChange = (value) => {
action('onChange')(value)
setArgs({ value: value })
setValue(value)
}
return ({
value: value,
onChange: onChange,
})
}and use in component's template: const Template = {
render: function Render(args) {
const { value, onChange } = useControlledProps(args)
return <>
<InputField
{...args}
value={value}
onChange={onChange}
/>
<button onClick={() => {onChange('ddd')}}>set "ddd" value</button>
</>
},
}```
For functional components it works good. |
Beta Was this translation helpful? Give feedback.
-
|
my preference for wrapping controlled components with stateful components (in React) is Decorators. you could pair this with Actions to spy on the event-handling interfaces. this would allow you to avoid building an ad-hoc logger inside the canvas. we use play functions to put stories into specific states — using testing-library events. |
Beta Was this translation helpful? Give feedback.
-
|
Starting from v9 or v10 (I was upgrading from v8) new problem appeared and my previous hook gives this error: on any edit. Here is my hook with imports: import { useState, useEffect } from 'react'
import { useArgs } from 'storybook/preview-api'
/**
* This hook connects any controlled component to Storybook's Controls block
* providing bi-directional updates of value and args.value when onChange is
* called or when args.value is changed in Controls block.
*
* Examples:
*
* import { useControlledProps } from '/.storybook/hooks' // eslint-disable-line import/no-absolute-path
*
* const Template = {
* render: function Render (args) {
* const [ value, onChange ] = useControlledProps()
* // value is needed here to prevent moving caret to the end in text inputs
* return <>
* <InputField {...args} onChange={onChange} value={value} />
* <button onClick={() => onChange('test')}>change value</button>
* </>
* },
* }
*
* const Template = {
* render: function Render (args) {
* const [ val, onClick ] = useControlledProps({ valueProp: 'val', changeProp: 'onClick' })
* return <>
* <InputField {...args} onClick={onClick} value={val} />
* <button onClick={() => onClick('test')}>change value</button>
* </>
* },
* }
*/
export const useControlledProps = (params) => {
const {
valueProp = 'value',
changeProp = 'onChange',
} = params || {}
const [args, setArgs] = useArgs()
const [value, setValue] = useState(args[valueProp] || '')
useEffect(() => {
setValue(args[valueProp])
}, [args[valueProp]])
const onChange = (value) => {
args[changeProp](value)
setArgs({[valueProp]: value })
setValue(value)
}
return ([value, onChange])
}It worked in v8. To make it work in v10 I had to change to Now there are no errors but new problem appeared: if I edit the text in the middle, cursor (caret) jumps to the end. It probably happens because React has special processing of synchronous events from inputs and Storybook's versions of You can see how it works in v10 here (I updated the sandbox to Storybook v10): Please consider updating the documentation and adding tests for controlled components. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Introduction
In my current code base, I commonly write stories like this:
That is, I have a controlled component. I want to test the behaviour of this component, allow the viewer to see how it behaves as you select new items.
To do this, I declare state in the storybook component.
What's in the docs?
There is the Actions essential plug in, which can detect click events etc. This appears to be good for detecting standard 'onClick' or 'onSubmit' type payloads, but doesn't appear to have a mechanism for setting some state.
The controls don't appear to have a function type arg, and in anycase, the use case I've got here actually pairs two of the args (onChange and selectedValue) together.
My investigation
I have done a bit of an investigation here.
We can also write a story like this:
This works absolutely fine.
What I'm wondering here, is am I missing a feature, or does Storybook otherwise have an opinion on how we would demonstrate this behaviour?
Beta Was this translation helpful? Give feedback.
All reactions