Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Main app does not load when importing and then conditionally rendering storybook in root index.js #147

Closed
omonk opened this issue Jan 14, 2021 · 12 comments

Comments

@omonk
Copy link

omonk commented Jan 14, 2021

Describe the bug
We're trying to run storybook-native alongside our main react-native app and conditionally loading storybook or the main app based off of an env variable. Problem is is that anytime we set our STORYBOOK_NATIVE env variable to false the main app fails to register and the simulator is blank.

Expected behavior
Main app loads when env variable is false

Code snippets

// index.js
import { AppRegistry } from "react-native";
import NativeStorybook from "./storybook-native"; // <-- this prevents the app from rendering
import MainApp from "./app";
import { STORYBOOK_NATIVE } from "react-native-dotenv";

const App = false ? null : MainApp;

AppRegistry.registerComponent(appName, () => App);
// storybook-native/index.js
import { getStorybookUI, configure } from "@storybook/react-native";

import { loadStories } from "./storyLoader";

configure(() => {
  loadStories();
}, module);

const StorybookUIRoot = getStorybookUI();

export default StorybookUIRoot;
// app.js
import "@react-native-firebase/app";
import "react-native-gesture-handler";
import "./src/sentry";
import "./src/i18n/i18n";

import MainApp from "./src/router";

export default MainApp;

System:

Environment Info:

  System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz
  Binaries:
    Node: 14.12.0 - /usr/local/bin/node
    Yarn: 1.22.5 - /usr/local/bin/yarn
    npm: 6.14.8 - /usr/local/bin/npm
  Browsers:
    Chrome: 87.0.4280.141
    Edge: 87.0.664.75
    Firefox: 84.0
    Safari: 13.1.3
  npmPackages:
    @storybook/addon-actions: ^5.3 => 5.3.21
    @storybook/addon-knobs: ^5.3 => 5.3.21
    @storybook/addon-links: ^5.3 => 5.3.21
    @storybook/addon-ondevice-actions: ^5.3.23 => 5.3.23
    @storybook/addon-ondevice-knobs: ^5.3.23 => 5.3.23
    @storybook/addons: ^5.3.12 => 5.3.12
    @storybook/preset-create-react-app: ^1.5.2 => 1.5.2
    @storybook/react: ^5.3.12 => 5.3.12
    @storybook/react-native: ^5.3.23 => 5.3.23
    @storybook/react-native-server: ^5.3.23 => 5.3.23
@dannyhw
Copy link
Member

dannyhw commented Jan 18, 2021

did you try reloading the app using the dev menu or cmd + R / ctrl+R

Also whats the purpose of this here ? @storybook/preset-create-react-app: ^1.5.2 => 1.5.2

Also can you please provide any errors that you have in the logs.

@dannyhw
Copy link
Member

dannyhw commented Mar 20, 2021

I've actually been able to reproduce this and its possible to resolve this by changing the way you call the getStorybookUI function.

instead of using

const StorybookUIRoot = getStorybookUI({
    asyncStorage: null
});
export default StorybookUIRoot;

do this

const getStorybookUIRoot = () => getStorybookUI({
    asyncStorage: null
});
export default getStorybookUIRoot

Then whenever you use the storybook ui make sure that you call this function like

import getStorybookUIRoot from "./storybook"

const storybookUI = getStorybookUIRoot()

export const App = ()=> <StorybookUI />

I'm still investigating the cause of this but this should help those that come across this. If you have any issues please let me know.

@ssilverberry
Copy link

ssilverberry commented Mar 29, 2021

@dannyhw Hello, I faced the issue too. Tried your workaround but it did not help. Storybook renders every time till I totally removed it from render. Want to admit I've restarted my app and metro server every time after any changes being provided.

I used the storybook in a such way.

/* index.js */

// components
import App from './app'
import StorybookUI from './storybook'
// env
import ENV from 'react-native-config'

AppRegistry.registerComponent(appName, () => (ENV.USE_STORYBOOK ? StorybookUI : App))

But it does not work even if use conditional render in the App, e.g.

/* storybook */

// configs
import { withKnobs } from '@storybook/addon-knobs'
import { getStorybookUI, configure, addDecorator } from '@storybook/react-native'
// utils
import './rn-addons'
import { loadStories } from './storyLoader'

// enables knobs for all stories
addDecorator(withKnobs)

// import stories
configure(() => {
  loadStories()
}, module)

const StorybookUIRoot = getStorybookUI({
  tabOpen: 0,
  onDeviceUI: true,
  asyncStorage: null,
})

export default StorybookUIRoot
/* app */

const App = () => {
  ...some stuff
  return ENV.USE_STORYBOOK ? <StorybookUI /> : <Wrapper>...other internal components</Wrapper>
}

export default App
/* index.js */

// components
import App from './app'
import StorybookUI from './storybook'
// env
import ENV from 'react-native-config'

AppRegistry.registerComponent(appName, () => App)

My System Info

System:
    OS: macOS 11.2.3
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 50.54 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 14.15.1 - ~/.nvm/versions/node/v14.15.1/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 6.14.8 - ~/.nvm/versions/node/v14.15.1/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.10.1 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 14.2, DriverKit 20.0, macOS 11.0, tvOS 14.2, watchOS 7.1
    Android SDK: Not Found
  IDEs:
    Android Studio: 4.0 AI-193.6911.18.40.6626763
    Xcode: 12.2/12B45b - /usr/bin/xcodebuild
  Languages:
    Java: 12.0.2 - /usr/bin/javac
  npmPackages:
    @react-native-community/cli: Not Found
    react: 17.0.1 => 17.0.1
    react-native: 0.64.0 => 0.64.0
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found

@dannyhw
Copy link
Member

dannyhw commented Mar 29, 2021

@goraug hey I'm not sure what exactly you tried but I'm pretty sure it would work if you changed it to be like:

/* storybook */

// configs
import { withKnobs } from '@storybook/addon-knobs'
import { getStorybookUI, configure, addDecorator } from '@storybook/react-native'
// utils
import './rn-addons'
import { loadStories } from './storyLoader'

// enables knobs for all stories
addDecorator(withKnobs)

// import stories
configure(() => {
  loadStories()
}, module)

const getStorybookUIRoot = () => getStorybookUI({
  tabOpen: 0,
  onDeviceUI: true,
  asyncStorage: null,
})

export default getStorybookUIRoot
/* app */
import getStorybookUIRoot from "./storybook"

const App = () => {
  ...some stuff

  if (ENV.USE_STORYBOOK ){
    const StorybookUI = getStorybookUIRoot()
    return <StorybookUI/>   
  } else {
    return <Wrapper>...other internal components</Wrapper>
  }
}

export default App

The key thing is not calling getStorybookUI unless storybook is actually being rendered.

@ssilverberry
Copy link

@goraug hey I'm not sure what exactly you tried but I'm pretty sure it would work if you changed it to be like:

/* storybook */

// configs
import { withKnobs } from '@storybook/addon-knobs'
import { getStorybookUI, configure, addDecorator } from '@storybook/react-native'
// utils
import './rn-addons'
import { loadStories } from './storyLoader'

// enables knobs for all stories
addDecorator(withKnobs)

// import stories
configure(() => {
  loadStories()
}, module)

const getStorybookUIRoot = () => getStorybookUI({
  tabOpen: 0,
  onDeviceUI: true,
  asyncStorage: null,
})

export default getStorybookUIRoot
/* app */
import getStorybookUIRoot from "./storybook"

const App = () => {
  ...some stuff

  if (ENV.USE_STORYBOOK ){
    const StorybookUI = getStorybookUIRoot()
    return <StorybookUI/>   
  } else {
    return <Wrapper>...other internal components</Wrapper>
  }
}

export default App

The key thing is not calling getStorybookUI unless storybook is actually being rendered.

Thank you, I try and let you know. My key point was to control storybook rendering via env variable cuz for some builds I would like to switch it on/off and don't edit code manually every time.

@ssilverberry
Copy link

@dannyhw Okay, I guess the problem is in the react-native-config lib or exposing env variables because if I do the same but change ENV.USE_STORYBBOK to a local variable instead then it works perfectly without any problems.

@dannyhw
Copy link
Member

dannyhw commented Mar 29, 2021

@goraug I usually will just create a json file and have a flag in there like

{
     showStorybook:true
}

Then if you want you can make a simple node script to change the value there. Then you can use that in your package.json to automate swapping between storybook and the app.

There is a somewhat relevant article here where they do something similar:
https://itnext.io/the-easiest-way-to-setup-multiple-environments-on-react-native-67b33d073390

But you don't really need to create multiple files for it, you can just write to the json file directly.

@ssilverberry
Copy link

@dannyhw Thank you, appreciate your help!

@elliotmrodriguez
Copy link

Hello, I would like to add to this, despite the fact that the issue is closed.

RN 0.63.4, existing application that we are wrapping SB around that is not an Expo application.

We have tried the above approach, and while it works great when we want to render the SB UI, it fails when we want to change the dependent variable. However, we noticed that simply importing the Storybook file, without calling the function to return the UI component at all, causes our main app to not render correctly. I see a couple of cycle warnings pointing to addons

require cycle: node_modules/@storybook/addons/node_modules/core-js/internals/microtask.js -> node_modules/@storybook/addons/node_modules/core-js/internals/microtask.js

Trying to wrangle this around conditional importing with no success. Seems similar to
storybookjs/storybook#1981

Wondering if anyone else has experienced anything similar since this issue was closed.

@dannyhw
Copy link
Member

dannyhw commented Dec 29, 2021

@elliotmrodriguez the require cycle is not related as far as I know. You can try using inline requires so as to not import storybook unless it's being used. Something like

if (SB){
  const getStorybook = require('./storybook')
 const Sbui = getStorybook ();
 return <Sbui/>
}

@elliotmrodriguez
Copy link

@elliotmrodriguez the require cycle is not related as far as I know. You can try using inline requires so as to not import storybook unless it's being used. Something like

if (SB){
  const getStorybook = require('./storybook')
 const Sbui = getStorybook ();
 return <Sbui/>
}

Thanks for the feedback @dannyhw I'll give it a shot.

@dannyhw
Copy link
Member

dannyhw commented Apr 16, 2022

I came across this issue again today and just adding here that a patch can be applied to solve the promises bug that also causes this issue see this comment here: #20 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants