From version 7.6.x to 8.3.x
In this version of storybook we’ve reworked the UI using some community react native packages. We’ve also overhauled the theme to match the web version.{
"devDependencies": {
"@storybook/react-native": "^8.3.5",
"@storybook/react": "^8.3.5",
"@storybook/addon-ondevice-controls": "^8.3.5",
"@storybook/addon-ondevice-actions": "^8.3.5",
"@storybook/addon-ondevice-backgrounds": "^8.3.5",
"@storybook/addon-ondevice-notes": "^8.3.5"
}
}
npx expo install react-native-reanimated react-native-gesture-handler @gorhom/bottom-sheet react-native-svg
npm install react-native-reanimated react-native-gesture-handler @gorhom/bottom-sheet react-native-svg
// metro.config.js
const path = require('path');
const withStorybook = require('@storybook/react-native/metro/withStorybook');
module.exports = withStorybook(config, {
// set to false to disable storybook specific settings
// you can use a env variable to toggle this
enabled: true,
// path to your storybook config folder
configPath: path.resolve(__dirname, './.storybook'),
});
From version 6.5.x to 7.6.x
In this version of storybook we’ve made a lot of changes to the internals of react native storybook to make it more compatible with core storybook libraries. This means compatibility with the new v7 store and the API changes that comes with that. Here are some of the improvements:- New storage option that lets you choose what storage solution you want to use (async storage/mmkv etc)
- Support for main.ts
- Dynamic imports enabled by the unstable_allowRequireContext option in metro config
- You only need to generate your requires file when main.ts changes
- Error boundaries for stories so your app shouldn’t crash when a story throws an error
- Improved markdown renderer for notes addon
- Simpler setup for auto args
{
"@storybook/react-native": "^7.6.10",
"@storybook/addon-ondevice-actions": "^7.6.10",
"@storybook/addon-ondevice-backgrounds": "^7.6.10",
"@storybook/addon-ondevice-controls": "^7.6.10",
"@storybook/addon-ondevice-notes": "^7.6.10"
}
It should now generate a
storybook.requires.ts file instead of a storybook.requires.js file. This provides the type for the new view export.Update
.storybook/index.js to use the new getStorybookUI function on the view exported from storybook.requires.ts. You can also change this file to be called .storybook/index.tsx.You should also now pass a storage object to the
getStorybookUI function. This is used to persist the selected story between reloads.// .storybook/index.tsx
import { view } from './storybook.requires';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StorybookUIRoot = view.getStorybookUI({
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
export default StorybookUIRoot;
Update your
metro.config.js to enable the unstable_allowRequireContext option and you can now remove the sbmodern resolver if you have it.You can also add here the generate function to automatically update the
storybook.requires.ts file when you start metro. You only need to regenerate this file now when main.js updates since requireContext allows us to use dynamic imports.// metro.config.js
const path = require('path');
const { getDefaultConfig } = require('expo/metro-config');
const { generate } = require('@storybook/react-native/scripts/generate');
generate({
configPath: path.resolve(__dirname, './.storybook'),
});
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
config.transformer.unstable_allowRequireContext = true;
config.resolver.sourceExts.push('mjs');
module.exports = config;
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');
const { generate } = require('@storybook/react-native/scripts/generate');
generate({
// update ./.storybook to your storybook folder
configPath: path.resolve(__dirname, './.storybook'),
});
const defaultConfig = getDefaultConfig(__dirname);
/**
* Metro configuration
* https://facebook.github.io/metro/docs/configuration
*
* @type {import('metro-config').MetroConfig}
*/
const config = {
transformer: {
unstable_allowRequireContext: true,
},
resolver: {
sourceExts: [...defaultConfig.resolver.sourceExts, 'mjs'],
},
};
module.exports = mergeConfig(defaultConfig, config);
You can now also remove anything from package.json scripts which would run generate before running storybook.
We’ve removed the types from
@storybook/react-native and now you should import them from @storybook/react. This is to remove duplication and increase compatibility with core storybook libraries.import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
component: Button,
argTypes: {
onPress: { action: 'pressed the button' },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Basic: Story = {
args: {
text: 'Press me!',
},
};
You can now also update main.js to main.ts and use the StorybookConfig type. This is one of the only types we export from @storybook/react-native in this version.
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [
'@storybook/addon-ondevice-notes',
'@storybook/addon-ondevice-controls',
'@storybook/addon-ondevice-backgrounds',
'@storybook/addon-ondevice-actions',
],
};
export default main;
import type { Preview } from '@storybook/react';
const preview: Preview = {
decorators: [],
parameters: {},
};
export default preview;
If you are manually adding doctools to do auto args you can now remove this code since its automatically added now. To make it work you still need babel-plugin-react-docgen-typescript though.
-import { extractArgTypes } from "@storybook/react/dist/modern/client/docs/extractArgTypes";
-import { addArgTypesEnhancer, addParameters } from "@storybook/react-native";
-import { enhanceArgTypes } from "@storybook/core/docs-tools";
-addArgTypesEnhancer(enhanceArgTypes);
-addParameters({
- docs: {
- extractArgTypes,
- },
-});
6.5.x to 7.6.x with storiesOf support
Storybook v7 doesn’t support storiesOf in the same way as v6, but there is a compatibility mode that allows you to continue using storiesOf whilst you migrate your stories.{
"@storybook/react-native": "^7.6.10",
"@storybook/addon-ondevice-actions": "^7.6.10",
"@storybook/addon-ondevice-backgrounds": "^7.6.10",
"@storybook/addon-ondevice-controls": "^7.6.10",
"@storybook/addon-ondevice-notes": "^7.6.10"
}
You should also now import storiesOf from
@storybook/react-native/V6 this is necessary so that certain code paths don’t run in v7 mode.Update the import in
.storybook/index.js from @storybook/react-native to @storybook/react-native/V6. You can also change this file to be called .storybook/index.tsx.You should also make sure to add the storage prop to the getStorybookUI call. This lets you opt into using a different storage solution like mmkv or if you put async storage there it will continue to work as it did before.
// .storybook/index.tsx
import './storybook.requires';
import { getStorybookUI } from '@storybook/react-native/V6';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StorybookUIRoot = getStorybookUI({
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
export default StorybookUIRoot;
import { storiesOf } from '@storybook/react-native/V6';
import { text } from '@storybook/addon-knobs';
import { withKnobs } from '@storybook/addon-ondevice-knobs';
import React from 'react';
import { Button } from './AnotherButton';
storiesOf('Another Button', module)
.addDecorator(withKnobs)
.add('another button example', () => <Button text={text('text', 'test2')} onPress={() => null} />)
.add('again', () => <Button text={text('text', 'text2')} onPress={() => null} />);
From version 5.3.x to 6.5.x
To make storybook more compatible with core storybook libraries we are using some new dependencies. You will need to add these to your project:
yarn add -D @storybook/react-native @storybook/core/common @react-native-async-storage/async-storage react-native-safe-area-context react-dom
yarn add -D @storybook/addon-ondevice-controls @storybook/addons @storybook/addon-controls @react-native-community/datetimepicker @react-native-community/slider
In 6.5 we use a script to generate your imports for stories and addons. This uses the new
main.js file to generate the storybook.requires.js file. This file is then imported into the index.js file.Remove the configure call, story imports and addon imports and reduce the index file to have this content. The most important thing is to import the
storybook.requires file.// .storybook/index.js
import { getStorybookUI } from '@storybook/react-native';
import './storybook.requires';
const StorybookUIRoot = getStorybookUI({
// options go here
});
export default StorybookUI;
In the addons field list the addons you want to use, these must be compatible with React Native Storybook. Addons made for React Native are usually prefixed with
addon-ondevice.// .storybook/main.js
module.exports = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [
'@storybook/addon-ondevice-controls',
'@storybook/addon-ondevice-actions',
'@storybook/addon-ondevice-backgrounds',
],
};
Move any global decorators and parameters you have to
preview.js, if you don’t have any then just export an empty array for decorators and an empty object for parameters.// .storybook/preview.js
import { View } from 'react-native';
export const decorators = [
// Using a decorator to apply padding for every story
(StoryFn) => (
<View style={{ flex: 1, padding: 8 }}>
<StoryFn />
</View>
),
];
export const parameters = {
my_param: 'anything',
};
In storybook 6.5 there are some new binaries that generate your story imports and one that watches for new files.
You’ll want to run the generate script whenever you add a new story file. Alternatively you can keep the watcher running.
We use the sbmodern resolver field in order to resolve the modern version of storybook packages. Doing this removes the polyfills that ship in commonjs modules and fixes multiple long standing issues such as the promises never resolving bug and more (caused by corejs promises polyfill).
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
--module.exports = getDefaultConfig(__dirname);
++const defaultConfig = getDefaultConfig(__dirname);
++defaultConfig.resolver.resolverMainFields.unshift('sbmodern');
++module.exports = defaultConfig;
module.exports = {
/* existing config */
resolver: {
resolverMainFields: ['sbmodern', 'react-native', 'browser', 'main'],
},
};
Whilst storiesOf will still work it is now deprecated so we recommend that you convert your stories to CSF.
There is a codemod for your convenience which should automatically make the code changes for you (make sure to update the glob to fit your files):
Replace the storiesOf API with a default export that defines the title and component of your stories. Replace the add method with named exports that define each story. If you have any parameters or decorators, you can add them to the default export or to stories.
storiesOf('Button', module)
.addParameters({ myParam: "anything" })
.addDecorator((Story)=> <Wrapper>{Story}</Wrapper>)
.add('primary', () => <Button primary label="Button" />)
.add('secondary', () => <Button label="Button" />);
export default {
title: 'Button',
component: Button,
// for all stories in this file
parameters: { myParam: "anything" },
};
export const Primary = { args: { primary: true, label: "button" } };
export const Secondary = {
// this gives the property "label" the default value "button"
args: { label: "button" },
// for just this story
decorators: [(Story) => (<Wrapper><Story/></Wrapper>)],
parameters: { myParam: "something else" },
};