ericvicenti / navigation-rfc

Better Navigation in React Native
441 stars 44 forks source link

AnimateView Rendering Scenes before they're "Ready"? #60

Closed domchristie closed 8 years ago

domchristie commented 8 years ago

I have following state structure:

const navigationState = {
  key: 'Root',
  children: [
    { key: 'Login' },
    { key: 'Dashboard' }
  ]
}

and a render method which looks something like:

<AnimatedView
  navigationState={navigationState}
  renderScene={renderScene}
/>

function renderScene (props) {
  return (
    <Card
      renderScene={({ scene }) => {
        const component = findComponent(scene.navigationState)
        return <component />
      }}
    />
  )
}

The 'Dashboard' (container) component requires some data which is requested on log in, however the AnimatedView renders all it's scenes when it's initialised (even though they're not visible or "active"). In my case this will throw an error.

How can I prevent a scene from rendering before it has the data it requires? It seems a bit daft to have to include conditionals for every component. I'm feel like I'm missing something here :/

jmurzy commented 8 years ago

You are not supposed to be pushing 'scenes' that you don't want rendered onto the stack. In your case, renderScene is called for both Login and Dashboard, and depending on how you set your index, you'll be shown the scene at that index.

What you want is to have it render Login, and upon successful login, call onNavigate with the appropriate action, and in your reducer, use NavigationStateUtils.push to manipulate your state to include Dashboard. Just like how you have it ^. Also, keep in mind that, unless you use NavigationView, your Login component will be pushed off screen and will not be "garbage collected". Depending on what you're trying to achieve, you may or may not want this.

🍺

domchristie commented 8 years ago

You are not supposed to be pushing 'scenes' that you don't want rendered onto the stack. In your case, renderScene is called for both Login and Dashboard, and depending on how you set your index, you'll be shown the scene at that index.

@jmurzy Thanks Jake, that’s great! I think I came to that conclusion eventually! I think part of my problem was that I was defining the structure of the navigation state tree, and then using that directly in my navigation components. I am now taking the approach of defining the structure separately and then slicing off the bits that are needed.

Thanks again

domchristie commented 8 years ago

upon successful login, call onNavigate with the appropriate action

Interesting. At the moment, the full purpose of onNavigate is a bit unclear to me. I’m using redux, and in my Login component, I’m calling dispatch(navigateTo('Dashboard')) in the componentWillReceiveProps hook:

componentWillReceiveProps (nextProps) {
  if (nextProps.session.user) this.dispatch(navigateTo('Dashboard'))
}

I’m only really using onNavigate to handle the swiping gesture to go back:

<NavigationAnimatedView
  navigationState={navigationState}
  renderScene={renderScene}
  onNavigate={() => { dispatch(navigateBack()) }}
/>

Perhaps I’m missing something again?

hedgerwang commented 8 years ago

@domchristie : I'm not familiar with redux. The flux way of doing this will be purely reactive without using onNavigate API.

class App extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      loggedIn: false,
      navigationState: {
        key: 'Root',
        children: [
          {key: 'Login'},
        ],
      },
    };
  }

  componentWillMount() {
    AppStateStore.addListener(() => {      
      if (!this.state.loggedIn && AppStateStore.isLoggedIn()) {
        this.setState({
          loggedIn: true,
          navigationState: {
            key: 'Root',
            children: [
              {key: 'Login'}, // You may want to remove this since use is already logged in.
              {key: 'DashBoard'}, 
            ],
          },
        });
      }
    });
  }

  render() {
    return (
      <AnimatedView
        navigationState={this.state.navigationState}
        renderScene={renderScene}
      />
    );
  }
}
domchristie commented 8 years ago

@hedgerwang Thanks.

onNavigate is a required prop of AnimatedView, so would you just pass in an empty function?

ericvicenti commented 8 years ago

@domchristie, Currently AnimatedView needs an onNavigate handler for things like the back button in the NavigationHeader, which may fire actions that need to be handled. If you don't have a header, or if you have a custom one, then you don't need it.

domchristie commented 8 years ago

FWIW: part of my solution now includes maintaining two navigation states: one for the logged out case (for Log In / Sign Up routes), and other other for the logged in case (the rest of the app). I have two reducers to handle each state and combine them with redux’s combineReducers. (Inspired in part by: http://blog.thebakery.io/react-native-experimental-navigation-with-redux/)