wix / react-native-navigation

A complete native navigation solution for React Native
https://wix.github.io/react-native-navigation/
MIT License
13.04k stars 2.67k forks source link

[V3][Android] componentDidAppear is not always invoked. #5467

Closed nickfla1 closed 4 years ago

nickfla1 commented 5 years ago

Issue Description

Sometimes pushing a component to the stack does not invoke its componentDidAppear method. Debugging I've noticed that the component is correctly added to the listeners map in ComponentEventsObserver.js but the method notifyComponentDidAppear is never called. I've also tried to debug the native code, as this method is invoked by a native event, but I was unable to reproduce this issue while debugging. Maybe the native code runs faster than the JavaScript application, firing the event before the component is even added to the mapping?

EDIT: This issue shows up only on the devices listed below below. We've also tested it on an Honor 10, a Samsung S10 and a Samsung Galaxy J3 2016 but the application runs without problems on these devices.

Environment

ItsNoHax commented 5 years ago

Hi @nickfla1 ,

Before we get the devs involved, would you be able to create a reproducible example?

fel-ri commented 5 years ago

I'm having the same problem, any solutions?

nickfla1 commented 5 years ago

@ItsNoHax

I will provide a demo project as soon as possible

randomBrainstormer commented 4 years ago

I'm having a similar issue for both Android and iOS. componentDidAppear is not being called after calling dismissModal(). Example: there's a modal currently showing, I launch another modal on top of that using showModal(), when I dismiss this modal the original one does not trigger the componentDidAppear() method. Not sure if is the expected behaviour in V3, but in V2 componentDidAppear() would be invoked. I'm also using 3.2.0 with react-native 0.61.1

vovka-s commented 4 years ago

I faced with this problem too. Closing of any modal screen doesn't fire componentDidAppear of underlying screen. RNN 0.60.5, RN 3.2.0

randomBrainstormer commented 4 years ago

As a workaround, I tried subscribing to the componentDidDisappear event as shown in the documentation and added a callback to update the view, and it seems to work.

componentDidAppear() {
    this.myNavListener = Navigation.events().registerComponentDidDisappearListener(({ componentId, componentName }) => {
        // stuff I do goes here
    });
}

componentDidDisappear(){
   this.myNavListener.remove()
}

However, is not really an optimal solution.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you believe the issue is still relevant, please test on the latest Detox and report back. Thank you for your contributions.

stale[bot] commented 4 years ago

The issue has been closed for inactivity.

idanlo commented 4 years ago

Can you reopen please? I am also experiencing this. We have tried to use multiple events instead of componentDidAppear, such as setRoot or push but none of them are as consistent as componentDidAppear was.

ItsNoHax commented 4 years ago

I went ahead and reopened this issue for you @idanlo . If you guys could provide a reproducible example, I can push this forward to the devs so they can plan it in their sprint.

guyca commented 4 years ago

@yogevbd Any chance this is related to animation completion blocks sometimes running on background thread?

hoanghuy0604 commented 4 years ago

I have this problem too. Firstly, I set a stackRoot with 4 tabs. 2nd, I push to a screen then I setRoot again with 4 tabs again. But when I tab on the second tab, ComponentDidAppear's not working. Log and have 3 times didmount and 2 times unmount as expected. All socket listener I have in ComponentDidMount not work also (Did unsubscribed these socket event on componentWillUnmount)

componentDidMount(){
        this.navigationEventListener = Navigation.events().bindComponent(this);
        console.log('loggedIn',this.props.loggedIn)
        console.log('sockettesting',this.props.socket)
        if (this.props.loggedIn && this.props.socket) {
            this.props.socket.on('reloadInbox', (data) => {
                console.log('Reload Inbox:', data);
                this.getDataWithSocket()
            });
            this.props.socket.on('createdBooking', (data) => {
                console.log('createdBooking', data);
                this.getDataWithSocket()
            })
            this.props.socket.on('TouristCancelledBooking',data => {
                console.log('cancelbookingdata',data);
                this.getDataWithSocket()
            })

        this.getConversations()
        }
    }

    shouldComponentUpdate(nextProps, nextState){
        return !_.isEqual(this.state.data, nextState.data)
    }

    componentWillUnmount() {
        console.log('unmounted')
        if (this.props.loggedIn && this.props.socket) {
            this.props.socket.off('reloadInbox')
            this.props.socket.off('createdBooking')
            this.props.socket.off('TouristCancelledBooking')
        }
        if (this.navigationEventListener) {
          this.navigationEventListener.remove();
        }
    }
stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you believe the issue is still relevant, please test on the latest version and report back. Thank you for your contributions.

Nemferno commented 4 years ago

I'm also having this issue where the root component in a stack or by itself invokes the componentDidAppear function; however, subsequent screens are not called. I tested whether or not the listener is actually listening to the other screen's appearance - it did.

I'll test this issue on a demo project and I will come back with results about this matter.

Nemferno commented 4 years ago

I came back with my results. I made a basic demo project using the example in the documentation.
The result is it works. Upon inspection of my own project, I noticed that using HOC components will not trigger subsequent components for the first time.

After few hours, I figured out that the problem lies in my HOC component.

Example of my screen registration:

Navigation.registerComponent("component", () => HOC(Component), () => Component);

The HOC component checks whether or not the user is authenticated. Since authentication is an async process, I render a blank screen instead of the actual wrapped screen so componentDidAppear does get executed for the blank screen (null) but rendering to the screen after authentication will not trigger componentDidAppear (the component with the defined componentDidAppear) since the overall HOC component has appeared to the user long before the actual screen is shown.

Solution:

const Provider = (Component) => (props) => {  
    const [ user, initializing ] = CustomHook();  
    return <Component initializing={initializing} user={user} {...props} />;  
}

I handle the initial componentDidAppear using componentDidUpdate and a stateful boolean property in my Component.

export default ExampleComponent extends Component {  
    constructor(props) {
        this.state = { init: false };
    }  

    componentDidUpdate() {  
        const { init } = this.state;
        const { initializing } = this.props;

        if (!init && !initializing) {
            // Do initial componentDidAppear
            this.setState({ init: true });
        }
    }
}

All good

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you believe the issue is still relevant, please test on the latest version and report back. Thank you for your contributions.

stale[bot] commented 4 years ago

The issue has been closed for inactivity.

ludescher commented 1 year ago

I had the same problem. After playing with the sample app, which works as expected,

I dug a little deeper. And as it turned out, I was missing a line.

class ExampleScreen extends NavigationComponent<Props> {
    constructor(props: Props) {
        super(props);

        Navigation.events().bindComponent(this); // <=== HERE
    }
}

Maybe this should be mention in the docs?

9r4ik commented 1 year ago

I had the same problem. After playing with the sample app, which works as expected,

I dug a little deeper. And as it turned out, I was missing a line.

class ExampleScreen extends NavigationComponent<Props> {
    constructor(props: Props) {
        super(props);

        Navigation.events().bindComponent(this); // <=== HERE
    }
}

Maybe this should be mention in the docs?

Same issue in ios app, how could I do it in function component?

I mention when u provide your component with this issue appears; ofc i register like Navigation.registerComponent("component", () => HOC(Component), () => Component);

my bottoms Tabs DESKTOP - ConferencesList - NotificationList - MENU my Logs

 LOG  componentDidDisappear DESKTOP
 LOG  componentDidAppear ConferencesList
 LOG  did appear ConferencesList DESKTOP
 LOG  did appear ConferencesList ConferencesList
 LOG  did appear ConferencesList
 LOG  componentDidDisappear ConferencesList
 LOG  did appear NotificationList DESKTOP
 LOG  did appear NotificationList ConferencesList
 LOG  componentDidDisappear NotificationList
 LOG  did appear MENU DESKTOP
 LOG  did appear MENU ConferencesList
 LOG  did appear MENU NotificationList

my hook

export const useMainBottomTabScreen = () => {
  const {componentId} = useContext(NavigationContext);
  const {setMainBottomTabCurrentComponentId} = useNavigationStateStore();

  useMemo(() => {
    const componentAppearListener =
      Navigation.events().registerComponentDidAppearListener(
        ({componentId: compId}) => {
          console.log('did appear', compId, componentId);
          if (compId === componentId) {
            console.log('did appear', compId);
          }
        },
      );
    const unsubscribe = Navigation.events().registerComponentListener(
      {
        componentDidAppear: () => {
          console.log('componentDidAppear', componentId);
          if (componentId) {
            setMainBottomTabCurrentComponentId(componentId);
          }
        },
        componentWillAppear: () => {
          console.log(' componentWillAppear', componentId);
          if (componentId) {
            setMainBottomTabCurrentComponentId(componentId);
          }
        },
        navigationButtonPressed: () => {
          console.log('navigationButtonPressed', componentId);
        },
        componentDidDisappear: () => {
          console.log('componentDidDisappear', componentId);
        },
      },
      componentId ?? '',
    );
    return () => {
      unsubscribe.remove();
      componentAppearListener.remove();
    };
  }, [componentId, setMainBottomTabCurrentComponentId]);
};

as u can see if i go fast from 1 to 2 to 3 to 4 componentDidAppear does not trigger

ludescher commented 1 year ago

@9r4ik have you tried registering the events directly?

export const useMainBottomTabScreen = () => {
  const { componentId } = useContext(NavigationContext);
  const { setMainBottomTabCurrentComponentId } = useNavigationStateStore();

  useMemo(() => {
    const componentAppearListener =
      Navigation.events().registerComponentDidAppearListener(
        ({ componentId: compId }) => {
          console.log('did appear', compId, componentId);
          if (compId === componentId) {
            console.log('did appear', compId);
          }
        },
      );
    const listeners = [
      Navigation.events().registerComponentDidAppearListener((event) => {
        console.log('componentDidAppear', event.componentId);
        if (event.componentId) {
          setMainBottomTabCurrentComponentId(event.componentId);
        }
      }),
      Navigation.events().registerComponentWillAppearListener((event) => {
        console.log(' componentWillAppear', event.componentId);
        if (event.componentId) {
          setMainBottomTabCurrentComponentId(event.componentId);
        }
      }),
      Navigation.events().registerNavigationButtonPressedListener((event) => {
        console.log('navigationButtonPressed', event.componentId);
      }),
      Navigation.events().registerComponentDidDisappearListener((event) => {
        console.log('componentDidDisappear', event.componentId);
      }),
    ];
    return () => {
      listeners.forEach((listener) => listener.remove());
      componentAppearListener.remove();
    };
  }, [componentId, setMainBottomTabCurrentComponentId]);
};

is there any difference in behaviour?

PS: I didn't test the sample, because i'm not familiar with functional components. Nevertheless, it should work just fine.