React-native: NavigationCard will only re-render when the route changes

Created on 24 May 2016  Â·  35Comments  Â·  Source: facebook/react-native

I'm trying NavigationExperimental and the NavigationCardStack.

But the renderScene isn't being call when the state is update.
You can test in the Examples/NavigationCardStackExample,when you press the direction doesn't happen anything only after you push new route a new direction is shown.

simulator screen shot 24 05 2016 13 37 58

Locked

Most helpful comment

This bug was introduced with the commit [bb39a2e]

Reduce extra rendering in NavigationCard

shouldComponentUpdate(nextProps: SceneViewProps, nextState: any): boolean { return ( nextProps.sceneRendererProps.scene.navigationState !== this.props.sceneRendererProps.scene.navigationState ); }

Summary: This adds a new SceneView with a shouldComponentUpdate policy of only re-rendering when the scene's state changes.

But what this code is doing is checking the navigationState.And it doesn't re-render if the scene state changes and the navigationState is the same.
This kind of check shouldn't be done by react-native, for the same reason that react has shouldComponentUpdate and doesn't do it automatically . It's very difficult to guess the intensions of the programmer.

All 35 comments

same problem here :(
since 0.26 then 0.26.2 renderScene() isn't call after state changes.

This bug was introduced with the commit [bb39a2e]

Reduce extra rendering in NavigationCard

shouldComponentUpdate(nextProps: SceneViewProps, nextState: any): boolean { return ( nextProps.sceneRendererProps.scene.navigationState !== this.props.sceneRendererProps.scene.navigationState ); }

Summary: This adds a new SceneView with a shouldComponentUpdate policy of only re-rendering when the scene's state changes.

But what this code is doing is checking the navigationState.And it doesn't re-render if the scene state changes and the navigationState is the same.
This kind of check shouldn't be done by react-native, for the same reason that react has shouldComponentUpdate and doesn't do it automatically . It's very difficult to guess the intensions of the programmer.

I still have this issue with 0.27.0, I had to go into the source code of RN and comment out those shouldComponentUpdate clauses 😞

recently updated from 0.25 to 0.29 and got the same problem. And I use Redux with my APP.

I also think it's caused by [bb39a2e], all the scenes are rendered by calling of render method of class SceneView in NavigationCard.js. So if you only change other states but navigationState, scenes won't re-render.

Could anyone confirm this is a bug or my misunderstanding?

I agree the shouldComponentUpdate policy here is a bit strict. But this is intentional to prevent extra rendering: we will only re-render a card when the route changes in the navState.

How would people like to see this change? We can't afford to re-render the insides of every scene whenever the navState changes, because that would result in several re-renders of every scene on every transition.

cc @ide @hedgerwang @jmurzy

I used redux and created "container" components that were connected to the state instead of depending on NavigationCard passing the state and updating the children components. Probably not the most elegant solution. Tried removing shouldComponentUpdate but made the app perform really bad on debug mode.

I'd say it's that the scene itself has to manage that itself. We'd recommend something like Your Scene component should implement shouldComponentUpdate to avoid excessive re-render.

How about allowing the user to send a boolean flag to force re-render of the scenes? I am also having issues using redux middleware store to re-render scenes.

@prabhaav but why we need this? We should shallow check route and index. If you want to re-render — send a new values.

@max-mykhailenko I am using redux for states and redux-saga for async functions. When the scenes receive new props, they should be re-rendered. With the current flow, the component NavigationCardStack is re-rendered, but the scenes are not re-rendered.

@prabhaav I work in the same way. In redux you don't change routes array, you create new. Shallow comparison can check it.

@max-mykhailenko not necessarily. In my case, the scene's are getting new props after the component is rendered. So if there's no change in the route stack, the scene's aren't re-rendered.

FYI: Disabling shouldComponentUpdate for class SceneView in NavigationCard.js fixed my issue.

@prabhaav connect scene directly to redux and it will receive props not from NavigationCard.

@max-mykhailenko Thanks Max that was a much cleaner solution!

I have the same problem, fixed it with a a redux container.

NavigationCard no longer implements shouldComponentUpdate (RN 0.33).

I'm probably missing something but since [email protected] whenever I push/reset a route renderScene is called twice... Is this behavior correct?
Should I implement myself the shouldComponentUpdate?
Any suggestions on the right place to do do this?
Also, does a place to discuss about Navigation Experimental exists?

Thanks guys.

@ericvicenti @ide

Snippet for reference, nothing fancy though

// CardStack
<CardStack
  navigationState={navigationState}
  renderHeader={this._renderToolbar}
  renderScene={this._renderScene}
  style={styles.container}
/>

// RenderScene
_renderScene = (props) => {
  const { currentRoute } = this.props
  switch (currentRoute.key) {
    case 'splashScreen': return <SplashScreen />
    case 'authScreen': return <AuthScreen />
    case 'mainScreen': return <MainScreen />
    default: return null
  }
}

// Reducer
export const initialState = {
  key: 'root',
  index: 0,
  routes: [routes.splashScreen]
}

export default (state = initialState, action) => {
  switch (action.type) {
    case actionTypes.PUSH: {
      const { route } = action
      if (state.routes[state.index].key === (route && route.key)) return state
      return navigationStateUtils.push(state, route)
    }
    case actionTypes.POP: {
      if (state.index === 0 || state.routes.length === 1) return state
      return navigationStateUtils.pop(state)
    }
    case actionTypes.RESET: {
      const { route } = action
      return { ...state, index: 0, routes: [route] }
    }
    default:
      return state
  }
}

The solution that is working is the following:

// CardStack
<CardStack
  navigationState={navigationState}
  renderHeader={this._renderToolbar}
  renderScene={(props) =>
    <SceneView
      sceneRenderer={this._renderScene}
      sceneRendererProps={props}
    />
  }
  style={styles.container}
/>

// Custom sceneRender wrapper
class SceneView extends Component {
  static propTypes = {
    sceneRenderer: PropTypes.func.isRequired,
    sceneRendererProps: PropTypes.any
  }

  shouldComponentUpdate (nextProps, nextState) {
    return (nextProps.sceneRendererProps.scene.route !== this.props.sceneRendererProps.scene.route)
  }

  render () {
    return this.props.sceneRenderer(this.props.sceneRendererProps)
  }
}

Yes it makes sense that it is rendering twice. Once when you update your navigation state and then again when the transition ends.

Your solution is basically reverting the commit that addressed this issue. I'll probably add something similar.

@ryankask
I noticed it not only re-render, it also mount, unmount and re-mount the component.

Interesting. I just upgraded and I'm not having that issue.

On 11 September 2016 at 10:38, Matteo Mazzarolo [email protected]
wrote:

@ryankask https://github.com/ryankask
I noticed it not only re-render, it also mount, unmount and re-mount the
component.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/facebook/react-native/issues/7720#issuecomment-246171159,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAE_44zw9Wo5mwMAJbbI29aXZIVnGpIOks5qo8wbgaJpZM4IlcyF
.

I'm having the same exact issue as @mmazzarolo.
Tested on 0.32 and 0.31 and works fine.

What happens with me is that I have some Container Component that fetches data.
That Component is pushed at some point and when I want to pop the route that's using that Component it first renders the previous scene, then the scene that is about to be "popped" and then the previous scene again (between I get an error because of that Container Component).

Also when I start the app the renderScene function renders twice.

@joaovpmamede Did you try wrapping the renderScene in like in my example?
I know, it's ugly, but it works

@mmazzarolo nop but I'll try that now.
Thanks.

9948 same 'render-twice' issue

I probably shouldn't ask this here but since no one in Discord answered me yet:

The issue that I'm having with scenes being re-rendered is that when I push a scene I also add a key (id) with a specific value (to the route object) so that the next scene Container Component (with connect) can use it to grab some data from a store.
After a pop that same view is re-rendered before it "goes way" but the state no longer has the id the mapStateToProps needs.
Right now I'm doing some conditionals on mapStateToProps but I don't like this and honestly I don't think I should.

What would be the correct approach?

state no longer has the id the mapStateToProps needs.

How are you accessing this id? Is that data coming from the renderScene props?

@ericvicenti the id is being added to the route object when I trigger the action that will be pushed to the routes array.

On the Container Component I have a mapStateToProps selector that will get data using state from navigation (where I get this id) and state from other reducer.

renderScene renders this Container Component with a conditional once I push a new route but as soon as I pop that route I get this issue of re-rendering the Container Component.

Yep, in NavigationCardStack, the scene gets re-rendered as it animates out. This is consistent with reality because the component still needs to exist for a moment during the transition.

The shouldComponentUpdate policy of the children can be set to prevent undesirable re-rendering. For a hint on how to do that, see the container that we removed in v0.33: https://github.com/facebook/react-native/commit/6c4d3c39c670e087930fcdae566e82db4b081113

@ericvicenti but because the selector function (from mapStateToProps) that grabs the data from the reducer runs before the child Component(where I could use the shouldComponetUodate) I still get this error.

Still not sure how to deal with this without having to check if the id is present. And if not there I have to create empty objects or arrays for the child Componet.

@mmazzarolo Did you find a work around for the Unmount, Remount issue. It causes havoc with the stack when popping back through. Handling a re-render shouldn't be an issue but an unmount trashes everything.

Hey @MossP, I'm still using the wrapper I posted above even if it's not the prettiest solution...

@mmazzarolo
That.
Is.
Awesome.

It didn't work for me the first time so I assumed it was a WIP or not for current RN version. A few typos later et voila. Thank you.

No worries man , glad it worked!

Thanks @mmazzarolo, I simply extended my SceneView from PureComponent and that did the trick

Was this page helpful?
0 / 5 - 0 ratings