Storybook and state (also uses @sambego/storybook-state)

There’s several ways to setup your components for testing within Storybook to allow us to change our component’s properties – using Knobs or creating wrapper components to encompass state changes through to hooking up to a redux store.

Let’s look at an example of wrapping our component within a story.

So I have an AppBar within a selection component. I want to see the story maintain the state changes when the user changes the selection. Now, no actual state is stored within the component itself as it’s passed up the component hierarchy to a parent component and/or redux store. This means, without setting up a parent component or redux store the selection changes but the actual value on the component remains the same.

One way to achieve this is to create a wrapper parent component, i.e. a minimal wrapper component around our component under test, for example

class WrapperComponent extends React.Component<any, any> {
  constructor(props) {
    super(props);

    this.state = { ...props }
  }

  render() {
    const props = { 
      ...this.props,
      onSelectionChanged: (selected, _) => this.setState({selected: selected}),
      selected: this.state.selected
    }

    return <ComponentUnderTest {...props} />
  }
}

So as you can see, the ComponentUnderTest is wrapped in a WrapperComponent and hooked up to props along with state changes, now we simply have the story return the WrapperComponent, i.e.

const props = {
  selected: 'None'
}

return <WrapperComponent {...props}/>

Easy enough and could be rewritten to make it more generic, but there’s an alternative and that’s to use something like @sambego/storybook-state

yarn add -D @sambego/storybook-state

In which case we can write our story like this instead

import { Store, State } from "@sambego/storybook-state";

const storeState = new Store({
  selectedPublicQuery: "None"
});

storiesOf("ComponentUnderTest", module)
  .add("default", () => {
    return (
      <State store={storeState}>
        {state => 
          <ComponentUnderTest selected={state.selected} 
              onSelectionChanged={(selected, _) => storeState.set({selected: selected})}/>
          }
        </State>
      );
  });