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

React Native this.props.navigator.push can't push a component twice for Navigator #3076

Closed
yogurt-island-331 opened this issue Sep 27, 2015 · 18 comments
Labels
JavaScript Resolution: Locked This issue was locked by the bot.

Comments

@yogurt-island-331
Copy link

When I use a function like this:

driverCreateTrip: function(){
    this.props.navigator.push({
      title: "New Trip",
      component: DriverCreateAccount,
    });
  },

, if I have already pushed the DriverCreateAccount component in a previous .js file, then it seems I can't use the push again, does it mean I have to use pop to get back to where I want to go? I can't push the same component twice to the stack?

@yogurt-island-331 yogurt-island-331 changed the title React Native iOS this.props.navigator.push can't push a component that was already pushed React Native iOS this.props.navigator.push can't push a component twice Sep 27, 2015
@yogurt-island-331
Copy link
Author

closing this issue since Facebook does not maintain NavigatorIOS

@yogurt-island-331 yogurt-island-331 changed the title React Native iOS this.props.navigator.push can't push a component twice React Native this.props.navigator.push can't push a component twice Sep 28, 2015
@yogurt-island-331
Copy link
Author

reopening this issue since I am having the same problem with Navigator

@yogurt-island-331
Copy link
Author

@brentvatne @ericvicenti

@hedgerwang
Copy link

@kevinzzz007

Not sure wether this is an issue with the Navigator or DriverCreateAccount itself. You can push any unique route into the navigator so theoretical what you do seems fine.

I wonder If you replace DriverCreateAccount with a simple dummy component, would you be able to reproduce the same issue?

@yogurt-island-331 yogurt-island-331 changed the title React Native this.props.navigator.push can't push a component twice React Native this.props.navigator.push can't push a component twice for Navigator Sep 28, 2015
@yogurt-island-331
Copy link
Author

@hedgerwang so it works if I push a component that hasn't been pushed before, but if I push something already on the stack then it gives me this error

Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of `Navigator`.reactConsoleError @ ExceptionsManager.js:86warning @ warning.js:42ReactElementValidator.createElement @ ReactElementValidator.js:322React.createClass.renderScene @ index.ios.js:34React.createClass._renderScene @ Navigator.js:1057(anonymous function) @ Navigator.js:1084React.createClass.render @ Navigator.js:1078ReactCompositeComponentMixin._renderValidatedComponentWithoutOwnerOrContext @ ReactCompositeComponent.js:715ReactCompositeComponentMixin._renderValidatedComponent @ ReactCompositeComponent.js:737ReactPerf.measure.wrapper @ ReactPerf.js:70ReactCompositeComponentMixin._updateRenderedComponent @ ReactCompositeComponent.js:673ReactCompositeComponentMixin._performComponentUpdate @ ReactCompositeComponent.js:654ReactCompositeComponentMixin.updateComponent @ ReactCompositeComponent.js:564ReactPerf.measure.wrapper @ ReactPerf.js:70ReactCompositeComponentMixin.performUpdateIfNecessary @ ReactCompositeComponent.js:490ReactReconciler.performUpdateIfNecessary @ ReactReconciler.js:112obj.(anonymous function) @ backend.js:2130runBatchedUpdates @ ReactUpdates.js:149Mixin.perform @ Transaction.js:140Mixin.perform @ Transaction.js:140assign.perform @ ReactUpdates.js:93flushBatchedUpdates @ ReactUpdates.js:173ReactPerf.measure.wrapper @ ReactPerf.js:70Mixin.closeAll @ Transaction.js:213Mixin.perform @ Transaction.js:154ReactDefaultBatchingStrategy.batchedUpdates @ ReactDefaultBatchingStrategy.js:66batchedUpdates @ ReactUpdates.js:108(anonymous function) @ MessageQueue.js:82guard @ MessageQueue.js:39processBatch @ MessageQueue.js:81messageHandlers.executeJSCall @ debugger-ui:66ws.onmessage @ debugger-ui:93
ExceptionsManager.js:65 Error: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of `Navigator`.
 stack: 
  instantiateReactComponent                          index.ios.bundle:8205
  Object.ReactChildReconciler.instantiateChildren    index.ios.bundle:22403
  ReactMultiChild.Mixin.mountChildren                index.ios.bundle:22112
  ReactNativeBaseComponent.Mixin.initializeChildren  index.ios.bundle:19420
  ReactNativeBaseComponent.Mixin.mountComponent      index.ios.bundle:19585
  Object.ReactReconciler.mountComponent              index.ios.bundle:6713
  Object.obj.(anonymous                              backend.js:2130
  ReactCompositeComponentMixin.mountComponent        index.ios.bundle:8511
  ReactPerf.measure.wrapper [as mountComponent]      index.ios.bundle:5875
 URL: undefined
 line: undefined
 message: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of `Navigator`.

@yogurt-island-331
Copy link
Author

@hedgerwang using a dummy component does work. To be more specific, I have a file called DriverCreateAccount.js, and have a view in it. I already pushed this onto the navigator, then when I try to do it again, and when I try to print out the route.component, it gives me object{}, but usually it gives me something like this

DriverCreateAccount.js:96 (props,context,updater){



if(__DEV__){
warning(
this instanceof Constructor,
'Something is calling a React component directly. Use a factory or ' + 
'JSX instead. See: https://fb.me/react-l…

so there is some issue with importing the .js file twice? Because importing it once works perfectly fine...

@yogurt-island-331
Copy link
Author

@yogurt-island-331
Copy link
Author

Coping the answer from the above Stackoverflow question:
This is a common problem with routing components. There are a couple of ways to approach this. In general, you want Alpha to avoid requiring Beta before Alpha has defined its exports and vice versa.

Approach 1: Lazy Loading

Alpha and Beta don't need each other until the user navigates from one scene to the next. For the app to reach this state, Alpha must have defined its exports (that is, module.exports = Alpha must have run for your app to have rendered an Alpha component). So, when the user is navigating to the scene displaying Beta, it is safe for Beta to require Alpha and therefore it is safe to require Beta at this point in time.

// Alpha.js
var Alpha = React.createClass({
goToBeta() {
// Lazily require Beta, waiting until Alpha has been initialized
var Beta = require('./Beta');

    this.props.navigator.push({
        component: Beta,
        title: Beta.title,
        wrapperStyle: styles.wrapper
    });
}

});
Although it isn't necessary to do the same for Beta.js in this specific scenario because Alpha is the first component loaded, it's probably a good idea so that your components all handle dependency cycles the same way.

Approach 2: Single Module

Another solution is to put Alpha and Beta in the same JS file to remove a cycle between modules. You would then export both components from the new mega-module.

// AlphaBeta.js
var Alpha = React.createClass({...});
var Beta = React.createClass({...});
exports.Alpha = Alpha;
exports.Beta = Beta;
To require it:

// index.js
var {Alpha, Beta} = require('./AlphaBeta');

@yogurt-island-331
Copy link
Author

Is there a best practice for this at Facebook? @hedgerwang

@ide
Copy link
Contributor

ide commented Sep 28, 2015

Pretty sure lazy loading is the best solution (I worked on JS modules and that specific implementation of require.js).

@ericvicenti
Copy link
Contributor

We don't do lazy-loading at Fb yet, but it would do the trick. The way we currently do this is to have a big mapping from routes to components that gets used in renderScene. That way we can require any route from a component without causing a circular dependency

@yogurt-island-331
Copy link
Author

@ide thanks! I will probs use that for now then @ericvicenti I see, could you share some examples on how to do this? Thank you so much!

@ericvicenti
Copy link
Contributor

Here is a highly simplified example of the approach we take:

this.navigator.push({ name: 'alpha' });
render: function() {
  return (
    <Navigator
      renderScene={this._renderScene}
    />
  );
},
_renderScene: function(route) {
  var Component = FallbackComponent;
  if (route.name === 'alpha') {
    Component = AlphaComponent;
  }
  return <Component />;
},

@yogurt-island-331
Copy link
Author

@ericvicenti thank you so much!

@yogurt-island-331
Copy link
Author

Just to clarify for future reference, this code worked for me:

this.props.navigator.push({ id: 'alpha' });
render: function() {
  return (
    <Navigator
      renderScene={this._renderScene}
    />
  );
},
_renderScene: function(route, nav) {
  var Component = FallbackComponent;
  if (route.id === 'alpha') {
    Component = AlphaComponent;
  }
  return <Component navigator={nav} />;
},

I used this as a reference:
https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/Navigator/NavigatorExample.js

@fselcukcan
Copy link

I am getting similar error on the simulator:
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. Check the render method of Navigator.

I have the following code structure:
in that gist.
I was taking the approach described by the @ericvicenti .
For Navigator to render scenes I have a renderScene(route, navigator) function.

What I am trying to get a navbar for Android myself.

@yogurt-island-331
Copy link
Author

@isikfsc I am on iOS so not sure how helpful this is, but you have var Component = route.component;, so this means you declare the require component statements in each of the individual views, which I didn't do. I declare all of them in ios.js and then use it there, so I don't declare anything in other views, maybe try this approach? I think this is also the approach that Facebook and @ericvicenti uses

@ivanbrens
Copy link

+1, this solution rocks. Thanks @ericvicenti and @kevinzzz007

@facebook facebook locked as resolved and limited conversation to collaborators Jul 21, 2018
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Jul 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
JavaScript Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

No branches or pull requests

8 participants