Skip to main content

Command Palette

Search for a command to run...

A Solution to Control Global State with Storybook

Published
2 min read
T

I'm a Front End Developer in Korea.

I work for a company called Lunit, that develops AI software to diagnose cancer.

From the UK, I came to Korea as an English teacher, and managed to retrain myself, and break out into the programming industry 😎

I recently started working with both Storybook and Recoil.

In order to make the Storybook controls faithful to the component's capabilities, it was necessary to somehow connect Storybook's controls to Recoil.

Storybook usually controls a component by changing values that are passed in as props - but to set this up, it might require changing the component to accept props that aren't going to be included in the component when it's used for real.

This defeats the purpose of a component library/Storybook - you shouldn't need to modify a component every time you implement it.

My solution was to create a component specifically for Storybook that implements the original component as well as a controller component.

import OriginalComponent from 'src/components'
import ComponentController from './ComponentController'

interface ComponentStoryProps {
  onClick: () => void // example prop for the main component 
  recoilProp: string // example prop to control Recoil/global state
}

function ComponentStory(args: ComponentStoryProps) {

  // innerArgs may be many more props
  const { recoilProp, ...innerArgs } = args 

  return (
    <>
      <ComponentController recoilProp={recoilProp} />
      <OriginalComponent {...innerArgs} />
    </>
  )
}

export default ComponentStory

Now inside the actual OriginalComponent.stories.ts file, I can add props that are passed into either the controller or the component itself.

Inside the controller is something like this:


import { useEffect } from 'react'
import useExampleHook from '.src/hooks/useExampleHook'

interface ComponentControllerProps {
  recoilProp: string
}

// using value 'props' here to make it easier to distinguish
// between properties with the same name.
function ComponentController(props: ComponentControllerProps) {
  const { componentState setComponentState } = useExampleHook()

  // Convert Storybook control props into recoil state updates
  useEffect(() => {
    if (props.recoilProp !== componentState.recoilProp) {
      setComponentState(props.recoilProp)
    }
  }, [props.recoilProp])

  return null
}

export default ComponentController

This component is responsible for checking its props (the Storybook's control state, then) against Recoil and updating it.

This will propagate into the main component via the hook.

In this example, I show a custom hook that allows access to global state. You can call useRecoilState in this component too, or set it up using any other global state - the same principle should work for other global state managers.

J

Tobias S This looks great. Would you be interested in writing a more in-depth guide/article on how to set up Storybook and Recoil and how to use it? The reason I'm asking this is we'd love to see some additional content about it. And also share it with the community?

Would love to hear your feedback on this? Let me know and we'll go from there.

Stay safe

1
T
Tobias S3y ago

Yeah, actually that might be useful for the community! It should also work with other state management solutions.

What do you guys have in mind?