Our stories are defined alongside our components in *.stories.js
. They are funnelled down through stories/stories.js
and stories/stories-native.js
and are then imported by stories/index.*.js
which exports the global story loader. See examples in any *.stories.js
for how to write a story loader. Once you've written a story, adding it to stories.js
(or stories-native.js
if it's a native-only story) will be enough to add it to the live storybook.
The yarn run storybook
command will spin up a local storybook server on localhost:6006
There's a native storybook UI that controls the selected story. You can also control the current native view by running yarn run rn-storybook
(with storybook running on a simulator) and navigating to localhost:7007
.
Storybook can be enabled by going to Build > Select Build Variant and selecting 'storyBook'. You can also run yarn rn-build-storybook-android && yarn rn-push-storybook-android
.
# Enable storybook web UI connection
adb reverse tcp:7007 tcp:7007
Storybook is controlled by the constants return value in Storybook.m
. Changing it to @{@"isStorybook": @true}
enables storybook mode.
We use an addon to storybook called Storyshots that adds jest tests on all our defined stories. yarn test
will run the jest tests and compare the output DOM to the stored snapshots, which are checked into the client repo. This means that updating components with stories can cause jest tests to fail if the snapshots haven't also been updated. Running yarn test -u
will update the snapshots to the current output.
You may want to have a component that accesses state with useSelector. The storybook for this component should use a mock store.
If a story has a child that is connected to the redux store, that child will still try to its connector in storybook mode, which will fail. To work around this, we patch connect
in storybook mode to access the store in a different way. connect
in storybook mode will assume the store is a map that looks like this:
{
[componentDisplayName: string]: ownProps => viewProps
}
The patched connect
will ignore anything defined in (mapStateToProps, mapDispatchToProps, mergeProps)
and try to call the closure (the prop factory) in the store. The prop factory will get whatever is in ownProps
and should return the props that the view expects.
In stories with connected children, a <Provider />
will need to wrap the top level (via addDecorator
) and contain this map. For convenience, createPropProvider
is exported from ./stories
that creates a <Provider />
from a map. ./prop-providers
contains some common prop factory creators, as well as a compose
function to combine multiple maps into a single <Provider />
. See devices/index.stories.js
for a sample implementation.
Making a prop factory requires knowing the display name of the connected component(s), which is generally the variable name of the component. Sometimes the display name fails to get piped through to the connector, in which case a setDisplayName(n)
will need to be chained immediately after the relevant connect call via recompose. Then the prop factory can be defined under n
.
Mocks are handled a couple of ways. How storybook and storyshots work is slightly different. Jest has some overrides in the package.json
file in the 'jest' section. Desktop storybook has some overrides with webpack in the .storybook/webpack.config.js
file. Native storybook has some overrides through metro in metro.config.js
.