React-native: Navigator transition end event

Created on 5 May 2015  路  16Comments  路  Source: facebook/react-native

Hey.
I've building an app where we have loading screen which when transits to login/home etc.
But how to I can replace first route in stack (to be able to use popToTop etc) after animation is ended?

  • componentDidMount runs just at the moment animation is started - which is working but basically prevents an animation.
  • timeout works but it's dirty.
  • onDidFocus is navigator level, not per route level.
  • https://github.com/facebook/react-native/issues/1153 should be able to fit in probably, but not yet :(.
Locked

Most helpful comment

Using refs to solve this is coupled code which is generally considered bad practice.

Best way IMO to solve this is by using an event bus which will notify any interested party. In this example I used our own EventBus class, but any will do.

onDidFocus(){
    EventBus.ref().emit('NAVIGATOR_TRANSITION_END')
}

render() {
    return (
        <Navigator
            ...
            onDidFocus={ this.onDidFocus }
            />
    )
}

And in the component:

componentWillMount() {
    EventBus.ref().addListener('NAVIGATOR_TRANSITION_END', this.onFocus, this)
}

All 16 comments

What I did was patch in #1154, then get the scene component's ref in onItemRef(ref, index, route) and add it as a field of my route object, and then in onDidFocus I called route.sceneRef.componentDidMount?().

i'll try this way, thx.
but still - some tightly coupling don't you think?

i guess so - high cohesion between related pieces

@ide

  1. Did you mean componentDidFocus?
  2. I don't think i follow. How do you get the reference to mounted component instance? and call a custom method?
  1. Oops. Yes I meant componentDidMount 馃檲
  2. The patch I linked to will make the Navigator pass the route to the onItemRef callback, so you can write:
type Route = {
  sceneRef?: ReactComponent;
};

<Navigator
  onItemRef={(ref, index, route) => {
    route.sceneRef = ref;
  }}
  onDidFocus={(route) => {
    route.sceneRef.componentDidFocus();
  }}
/>

@ide
Thank you very much, everything is working. Only problem here is scenereRef being renderScene component, not actually component of the route which forces to proxy this kind of events down the refs chain. Not very clean but way better than any other solution i could think of without much of a pain.

i'll close this issue and paste some code for any future references:

navigator component

<Navigator
        ref="nav"
        style={styles.container}
        configureScene={this.sceneConfig}
        renderScene={this.renderScene}
        onItemRef={function(ref, index, route) {
            route.sceneRef = ref;
          }
        }
        onDidFocus={
          function(route) {
            route.sceneRef.sceneDidFocus();
          }
        }
        initialRoute={{
        ...
      }}
    />

renderScene

renderScene(route, navigator) {
    return (
      <AppScene
        component={route.component}
        navBar={route.navigationBar}
        navigator={navigator}
        route={route}
      />
    );
  },

AppScene.js

var Scene = React.createClass({
 sceneWillFocus: function() {
    var component = this.refs.component;

    // do any checks needed here
    component.componentWillFocus();
  }, 

sceneDidFocus: function() {
    var component = this.refs.component;

    // do any checks needed here
    component.componentDidFocus();
  },


  render: function() {
    var Component = this.props.component;
    var navBar = this.props.navBar;

    return (
      <View style={styles.scene} ref="scene">
        {navBar}

        <Component {...this.props} ref="component"/>

      </View>
    );
  }
});

This will allow to use Will/DidFocus callbacks defined on component level;

But still we will need better "navigator view" handling:

  1. hiding next/back button;
  2. replacing next/back button text;
  3. replacing next/back button components (with ActivityIndicator for example);
  4. defining next/back button press handlers on component passed to route level, as it's clear that this logic belongs to component, not subject pushed this component to route;
  5. defining will/did focus/blur callbacks on component level, not passing it manually through scene rendering method; It's very important since componentWillUnmount does not called with navigator transition so there is no way to clear timeouts, unsubscribe from events etc.

But i believe it is whole other issue.

I agree there needs to be a better way of sending the "componentDidFocus" signal down the component hierarchy. This is especially important when you have nested Navigators, which the solution I offered does not solve.

Using refs to solve this is coupled code which is generally considered bad practice.

Best way IMO to solve this is by using an event bus which will notify any interested party. In this example I used our own EventBus class, but any will do.

onDidFocus(){
    EventBus.ref().emit('NAVIGATOR_TRANSITION_END')
}

render() {
    return (
        <Navigator
            ...
            onDidFocus={ this.onDidFocus }
            />
    )
}

And in the component:

componentWillMount() {
    EventBus.ref().addListener('NAVIGATOR_TRANSITION_END', this.onFocus, this)
}

Dear @PierBover, Your solution looks pretty awesome!馃憤
But I can't find any docs about EventBus, How can I use this in my project.

Thanks @h2oiswater !

EventBus is a class we have developed in house, so you won't find any documentation on it.

You could use actions with Redux, or any other event bus library such as PubSubJS, EventBus (not the one we are using), or make your own.

If you're going to use the EventBus the above code has a memory leak. You need 'removeListener' as well.

If you're going to use the EventBus the above code has a memory leak. You need 'removeListener' as well.

Obviously. That code only served for illustrative purposes.

It's not obvious to newbie programmers.
Never assume people know everything.

Memory leaks can kill projects and they are hard to debug.

Memory leaks can kill projects and they are hard to debug.

That's true. I should have probably also included a guide about design patterns and good practices with my example.

For anyone else reading, make sure to include (For example)

componentWillUnmount() {
    EventBus.ref().RemoveListener('NAVIGATOR_TRANSITION_END', null, this)
}

When using any sort of event driven library.

InteractionManager might be what you're looking for and does anyone else still prefer deprecated Navigator over the other nav libraries?

https://github.com/facebook/react-native/issues/2313#issuecomment-269448381

Was this page helpful?
0 / 5 - 0 ratings