jlyman/RN-NavigationExperimental-Redux-Example

Help: renderScene is executed just once and does not refresh updated props

chogarcia opened this issue · 15 comments

I got a onChangeTab function that sends the action to push title to the AppContainer.
I got it working but for some reason renderOverlay is re-rendering and updating the title but renderScene is executed just once.

Please see comments in the code below:

    class AppContainer extends React.Component {    
      render() {
        return (
          <NavigationCardStack
            navigationState={this.props.navigationState}
            onNavigate={this.props.onNavigate}
            renderOverlay={props => {
               // Here title updates and executes every time I change to another tab. 
               console.log(props.navigationState.title, 'title');
               return (
                   <Text>{props.navigationState.title || 'Title'}</Text>
               )
            }}
            renderScene={(props) => {
              // Here does not change because it is executed just one.
              console.log(props.navigationState.title, 'title');
                  return (
                    <View>
                      <View style={styles.appbar}>
                        <Text style={styles.title}>{props.navigationState.title || 'Title'}</Text>
                      </View>
                      <View>
                        <TabsView />
                      </View>
                    </View>
                  )
            }}
          />
        )
      }
    }

    const mapStateToProps = (state) => {
      // Here title updates every time I change to another tab.
      console.log(state.navigationState.title);
      return {
        navigationState: state.navigationState
      }
    }

    const mapDispatchToProps = (dispatch) => {
      return {
        onNavigate: (action) => {
          if (action.type && (
            action.type === NavigationRootContainer.getBackAction().type ||
            action.type === NavigationCard.CardStackPanResponder.Actions.BACK.type)
          ) {
            dispatch(navigatePop())
          } else {
            dispatch(navigatePush(action))
          }
        }
      }
    }

    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(AppContainer)

Here is my reducer:

    function navigationState(state = initialNavState, action) {
        switch (action.type) {
            case TITLE_PUSH:
                return {
                    ...state,
                    title: action.title
                }
            ...

Help or hints would me much appreciated.

I'm getting a similar problem; however my renderscene is running every time with the correct scene props. I'm not really sure what's going on. Basically I have 5 tabs and for each of those tabs I'm reseting the NavState (this seems to make sense for me).
Then I have an overlay with my tabs and when attempting to navigate I reset the routes for each tab. It seems like everything is working correctly and it does for the first two tab changes and then after that it fails (it's not always the same tabs - I can make it fail with any combination of tabs). I'm really not sure how to debug this problem. It's really frustrating.

In my overlay:

  navigateTo(scene) {
    if (scene !== this.props.navigationKey) {
      this.props.navigateReset([{ key: scene }], 0);
    }
    return null;
  }

in my reducer:

case NAV_RESET: {
      return {
        ...state,
        index: action.index,
        children: action.children,
      };
    }
wsun commented

@cho-is I believe the issue you are describing is the result of inserting a custom property into your navigationState object that renderScene doesn't care about. renderScene appears to only be called when SceneView thinks it should update, and SceneView is only paying attention to the scene property in props.

https://github.com/facebook/react-native/blob/1e626027f58093297ee3b5b73baa9a645ff6a36e/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js#L75

I've observed similar situations when playing around with NavigationExperimental - the library assumes several constraints which haven't been properly documented yet, and this is an example of one of them.

@wsun Any idea on my issue. That makes sense, but my renderscene function is in fact running with the correct route being passed. For whatever reason the actual rendering isn't taking place. Oh and I will add that if I change all of the scenes so that they are navigating based on a jumpTo or push (which isn't what I want) then the scenes do in fact render correctly. It seems to be associated with the reset action that's the problem.

wsun commented

@sscaff1 hard for me to figure out what's going on based only on the snippets you've posted. Is your navigationState object what you expect after each reset?

@wsun When I navigate to each tab all of my NavigationStates look the same. When I click on another tab (after the first one or two it won't render again remember), but it'll log the Navigation state below with the correct scene.

{
  key: 'MainNavigation',
  index: 0,
  children: [
    { key: scene }
  ]
}

Here is my render method:

  render() {
    const { navigationState, onNavigate } = this.props;
    console.log(navigationState);
    return (
      <NavigationAnimatedView
        style={styles.container}
        navigationState={navigationState}
        onNavigate={onNavigate}
        renderOverlay={this.renderOverlay}
        renderScene={props => (
          <NavigationCard
            {...props}
            renderScene={this.renderScene}
            key={props.scene.navigationState.key}
          />
        )}
      />
    );
  }

Here is my render Scene:

renderScene({ scene }) {
    const { navigationState } = scene;
    switch (navigationState.key) {
      case 'LoginScene':
        return <LoginScene />;
      case 'SignupScene':
        return <SignupScene />;
      case 'ForgotPasswordScene':
        return <ForgotPasswordScene />;
      case 'FeedScene':
        return <FeedScene />;
      case 'FeedItemScene':
        return <FeedItemScene />;
     ....
     case default: 
        return <LoginScene />
    }
}
wsun commented

Have you also verified that the scene object in renderScene is what you would expect? When you describe it not working after a couple tab switches, is that render not being called? or renderScene? or both are called, but you see no visual update?

Both are called with the correct NavigationState and yet I see no visual update.

@wsun I'm also using redux persist and when I refresh the app (cmd + R), I'm on the scene that I should have been on if a visual update had occurred. In addition, some of my routes have promises that go fetch some data when I click onto those tabs and the promises resolve as well, but again no visual update.

wsun commented

Interesting, so renderScene has the right navigationState and returns the right component every time, but after a couple times, returning the right component means nothing and the UI doesn't get updated? Based on the code you've shared, I can't think of anything that would cause this... maybe worth sharing with the team working on this library.

Perhaps better to just stick with jumpTo for your tabs rather than resetting.

Can I just say that you all are awesome for jumping on this issue as part of the community? 😄

I haven't had a chance to read in depth, but just one thing to check: do any of your rendered scenes need keys by chance? I remember having an issue back in the early days (may not apply anymore) where I was rendering out the same component multiple times, but not seeing refreshes because React couldn't tell if the component had changed. Adding unique keys to my components in renderScene() resolved it.

@jlyman you mean the key on the NavigationState itself? This is what my NavigationState looks like for each scene:

{
  key: 'MainNavigation',
  index: 0,
  children: [
    { key: scene }
  ]
}

So give the state a unique key on each tab render (like the scene name)?

Nope sorry, I mean in the React Components that get rendered out. Something like this:

_renderCard({ scene }) {
        const { navigationState } = scene
        const [destination, pageKey] = navigationState.key.split('::')
        let sceneContainer

        switch (destination) {
        case 'NormalScreen':
            sceneContainer = <NormalScreenContainer />
            break
        case 'KeyedScreenCuzMultiples':
            sceneContainer = <KeyedScreenCuzMultiplesContainer key={pageKey} />
            break
        default:
            sceneContainer = <SignInContainer />
        }

        return sceneContainer
    }

Observe the key getting added to the second case's component.

@jlyman Hmmm. Ok well now I'm getting somewhere.

  renderCard(props) {
    const { scene } = props;
    const [destination, pageKey] = scene.navigationState.key.split('::');
    console.log(navigationState.key);
    console.log(destination, pageKey);
    return (
      <NavigationCard
        {...props}
        renderScene={this.renderScene}
        key={props.scene.navigationState.key}
      />
    );
  }

renderCard's key above is running multiple times and the final key is always the route that I'm stuck on. If I log the route on renderScene though it's the correct route...So it doesn't seem like render card is necessarily updating even though render scene is running?

Oh and page key is always undefined...

@sscaff1 What version of React Native are you using? NavigationExperimental is a moving target and it'd be hard to debug without knowing the exact version used.

A quick question, are you rendering a NavigationCard' in your renderScene or those are plain components? If so, can you replace them with NavigationCard's and try to reproduce?

<NavgationCard
  key={scene.navigationState.key}
  {...props}
  renderScene={this.renderCardScene}
/>

🍺

This is probably old, so closing. Feel free to re-open if need be.