pendo-io / pendo-mobile-sdk

Pendo captures product usage data, gathers user feedback, and lets you communicate in-app to onboard, educate, and guide users to value
https://www.pendo.io
Other
59 stars 2 forks source link

Navigation object hasn't yet been initialized #121

Closed nvacheishvili closed 6 months ago

nvacheishvili commented 8 months ago

Platform + Version IOS 17.2 and others

SDK Version 3.1.1

Framework React Native using React Navigation

"@react-navigation/drawer": "^6.4.2",
"@react-navigation/native": "^6.0.10",
"@react-navigation/native-stack": "^6.6.2",
"@react-navigation/stack": "^6.2.1",
"rn-pendo-sdk": "^3.1.1"

Describe the bug App crashes because Pendo navigation tries to use navigation object when its not initialized.

Expected behavior A clear and concise description of what you expected to happen.

Logs [rn-pendo-sdk] React Navigation Init. INFO [rn-pendo-sdk] React Navigation Init. INFO [rn-pendo-sdk] React Navigation Init. The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.

 INFO  [rn-pendo-sdk]  React Navigation Init.
 DEBUG  [rn-pendo-sdk]  WithPendoReactNavigation was called
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onDynamicContentRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onCaptureRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onReturnFromAlertRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onDefaultRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onScreenContentChange
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onModalStateVisible
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onModalStateHidden
 DEBUG  [rn-pendo-sdk]  PNDContainer onReady
 DEBUG  [rn-pendo-sdk]  propagating onReady
 DEBUG  [rn-pendo-sdk]  Screen not changed.
 DEBUG  [rn-pendo-sdk]  Pendo Plugin 3.1.2, React Native: 0.73.4
 INFO  [rn-pendo-sdk]  React Navigation Init.
 DEBUG  [rn-pendo-sdk]  WithPendoReactNavigation was called
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onDynamicContentRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onCaptureRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onReturnFromAlertRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onDefaultRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onScreenContentChange
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onModalStateVisible
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onModalStateHidden
 DEBUG  [rn-pendo-sdk]  PNDContainer onReady
 DEBUG  [rn-pendo-sdk]  propagating onReady
 DEBUG  [rn-pendo-sdk]  Pendo Plugin 3.1.2, React Native: 0.73.4
 INFO  [rn-pendo-sdk]  React Navigation Init.
 DEBUG  [rn-pendo-sdk]  WithPendoReactNavigation was called
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onDynamicContentRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onCaptureRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onReturnFromAlertRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onDefaultRequestAnalyzer
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onScreenContentChange
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onModalStateVisible
 DEBUG  [rn-pendo-sdk]  addScanRequestListener: onModalStateHidden
 DEBUG  [rn-pendo-sdk]  PNDContainer onReady
 DEBUG  [rn-pendo-sdk]  propagating onReady
 ERROR  The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted, or if the navigator hasn't finished mounting. See https://reactnavigation.org/docs/navigating-without-navigation-prop#handling-initialization for more details.
 DEBUG  [rn-pendo-sdk]  Screen not changed.
 DEBUG  [rn-pendo-sdk]  NavigationBar discovered  423
 DEBUG  [rn-pendo-sdk]  Root Tag discovered:  423
 DEBUG  [rn-pendo-sdk]  Root Tag discovered:  313
 DEBUG  [rn-pendo-sdk]  NavigationBar discovered  309
 DEBUG  [rn-pendo-sdk]  NavigationBar discovered  309
 DEBUG  [rn-pendo-sdk]  Root Tag discovered:  403
 DEBUG  [rn-pendo-sdk]  analyzeTree - screenId: LogIn

Sample Code

function initPendo() {
    const navigationOptions = {library: NavigationLibraryType.ReactNavigation};
    const pendoKey = Config.PENDO_API_KEY;
    //note the following API will only setup initial configuration, to start collect analytics use startSession
    PendoSDK.setup(pendoKey, navigationOptions);
    PendoSDK.setDebugMode(true);
  }
  initPendo();
import * as React from 'react';
import RootNavigation from './RootNavigation';
import analytics from '@react-native-firebase/analytics';
import {NavigationContainer} from '@react-navigation/native';
import {WithPendoReactNavigation} from 'rn-pendo-sdk';
import {navigationRef} from './NavigationService';

const NavigationSetup = () => {
  const routeNameRef = React.useRef();
  const PendoNavigationContainer =
    WithPendoReactNavigation(NavigationContainer);

  return (
    <React.Fragment>
      <PendoNavigationContainer
        ref={navigationRef}
        onReady={() => {
          routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name;
        }}
        onStateChange={async () => {
          const previousRouteName = routeNameRef.current;
          const currentRouteName =
            navigationRef.current?.getCurrentRoute()?.name;

          if (previousRouteName !== currentRouteName) {
            await analytics().logScreenView({
              screen_name: currentRouteName,
              screen_class: currentRouteName,
            });
          }
          routeNameRef.current = currentRouteName;
        }}>
        <RootNavigation />
      </PendoNavigationContainer>
    </React.Fragment>
  );
};

export default NavigationSetup;
// import * as React from 'react';
import {createNavigationContainerRef} from '@react-navigation/native';

export const navigationRef = createNavigationContainerRef();
// tried it like this too and still didn't work
// export const navigationRef = React.createRef();

export function navigate(name, params) {
  //This function just does not get called when the pendo navigation is launching and that's why these are not shown in the logs
  if (navigationRef.isReady()) {
    console.log(`Navigating to ${name}`);
    navigationRef.current?.navigate(name, params);
  } else {
    console.log(`Attempted to navigate to ${name} when it is not ready`);
  }
}

Additional context Add any other context about the problem here.

MikePendo commented 8 months ago

@nvacheishvili Can you try to move const PendoNavigationContainer = WithPendoReactNavigation(NavigationContainer); out of NavigationSetup (in general its a best practice)

nvacheishvili commented 8 months ago

Hi @MikePendo for sure, should it be an exportable constant do you think in the NavigationService file or do you imagine it to be inside App.js file?

MikePendo commented 8 months ago

just inside the App.js I mean lets first see and try if that resolve the issue.

nvacheishvili commented 8 months ago

I did both approaches and none of them helped

  1. this is how my App.js file was looking
const PendoNavigationContainer =
    WithPendoReactNavigation(NavigationContainer);
  const routeNameRef = React.useRef();

  return (
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <SplitContext.Provider value={splitValue}>
          <NetworkProvider />
          <NativeBaseProvider theme={nativeBaseThemeSetup}>
            <React.Fragment>
              <PendoNavigationContainer
                ref={navigationRef}
                onReady={() => {
                  routeNameRef.current =
                    navigationRef.current.getCurrentRoute().name;
                }}
                onStateChange={async () => {
                  const previousRouteName = routeNameRef.current;
                  const currentRouteName =
                    navigationRef.current.getCurrentRoute().name;

                  if (previousRouteName !== currentRouteName) {
                    await analytics().logScreenView({
                      screen_name: currentRouteName,
                      screen_class: currentRouteName,
                    });
                  }
                  routeNameRef.current = currentRouteName;
                }}>
                <RootNavigation />
              </PendoNavigationContainer>
            </React.Fragment>
          </NativeBaseProvider>
        </SplitContext.Provider>
      </PersistGate>
    </Provider>
  );
  1. for exported constant NavigationSetup.js
    
    import * as React from 'react';
    import RootNavigation from './RootNavigation';
    import analytics from '@react-native-firebase/analytics';

import {navigationRef, PendoNavigationContainer} from './NavigationService'; // import {WithPendoReactNavigation} from 'rn-pendo-sdk'; // import {NavigationContainer} from '@react-navigation/native';

const NavigationSetup = () => { const routeNameRef = React.useRef();

return (

{ routeNameRef.current = navigationRef.current.getCurrentRoute().name; }} onStateChange={async () => { const previousRouteName = routeNameRef.current; const currentRouteName = navigationRef.current.getCurrentRoute().name; if (previousRouteName !== currentRouteName) { await analytics().logScreenView({ screen_name: currentRouteName, screen_class: currentRouteName, }); } routeNameRef.current = currentRouteName; }}> ); }; export default NavigationSetup; ``` NavigationService.js ``` import * as React from 'react'; import {WithPendoReactNavigation} from 'rn-pendo-sdk'; import {NavigationContainer} from '@react-navigation/native'; export const navigationRef = React.createRef(); export function navigate(name, params) { navigationRef.current?.navigate(name, params); } export const PendoNavigationContainer = WithPendoReactNavigation(NavigationContainer); ``` 3. I also tried exporting it from App.js file and it didn't work out Any other suggestions?
nvacheishvili commented 8 months ago

Also, I am not sure if this bit of an information would help or not but with just using NavigationContainer and having the pendo setup but without PendoNavigationContainer no errors are thrown, I am thinking if the onReady and onStateChange functions have an issue with PendoNavigationContainer

EDIT: I deleted the onReady and onStateChangeMethod and just left a reference there, and the issue still persists

MikePendo commented 8 months ago

@nvacheishvili Will you be able to create a small sample app (something minimal) that reproduce the issue and we will debug it on our side?

nvacheishvili commented 8 months ago

@MikePendo I understood that its definitely an issue with onReady functionality within pendo, because it assumes navigator object is ready when its not...

MikePendo commented 8 months ago

ok let us know if we can close the issue

nvacheishvili commented 8 months ago

It's likely worth investigating further, as I suspect others may encounter this issue as well.

I devised a solution by leveraging the navigation reference within the definition of a component that yields a navigation component, like Stack, for instance.

The challenge arose because we employed useEffect hooks within the component. This approach works seamlessly when the component resides inside a NavigationContainer. However, complications arise when it is placed within a PendoNavigationContainer as I think it calls onReady function too early, when the navigation object is actually not available.

Here's an example of the cause of the issue:

import {navigate} from './NavigationService';

// where NavigationService contains the following:
// import {createNavigationContainerRef} from '@react-navigation/native';
// export const navigationRef = createNavigationContainerRef();
//
// export function navigate(name, params) {
//   navigationRef.current?.navigate(name, params);
// }

const NotAuthenticated = () => {
  const dispatch = useDispatch();
  const isConnected = useSelector(state => state.app.isConnected);
  const isLoggedIn = useSelector(state => state.user.isLoggedIn);
  const user = useSelector(state => state.user.user);

  const [hasPin, setHasPin] = useState(null);
  const [isTokenValid, setIsTokenValid] = useState(false);

  useEffect(() => {
    if (!isConnected) {
      checkPin();
      return;
    }
    if (isLoggedIn) {
      if (user?.mfaStatus === 'Pending') {
        return navigate(Routes.MFARegistration);
      }
      if (user?.mfaStatus === 'Enabled') {
        return navigate(Routes.MFAVerification);
      }
    } else {
      checkPin();
    }
  }, [isLoggedIn, user.mfaStatus, isConnected]);

  return (
    <Stack.Navigator
      initialRouteName={
        isTokenValid && hasPin ? Routes.PinLogin : Routes.LogIn
      }>
      <Stack.Screen
        name={Routes.ChangeAccount}
        component={ChangeAccount}
        options={{
          header: () => null,
          gestureEnabled: false,
        }}
      />
      <Stack.Screen
        name={Routes.LogIn}
        component={LogIn}
        options={loginHeader}
      />
      <Stack.Screen
        name={Routes.MFARegistration}
        component={MFARegistration}
        options={{
          header: () => null,
          gestureEnabled: false,
        }}
      />
      <Stack.Screen
        name={Routes.MFAVerification}
        component={MFAVerification}
        options={{
          header: () => null,
          gestureEnabled: false,
        }}
      />
      <Stack.Screen
        options={{
          header: () => null,
        }}
        name={Routes.PinLogin}
        component={PinLogin}
      />
      <Stack.Screen
        options={{
          header: () => null,
        }}
        name={Routes.QaTestSetup}
        component={QaTestSetup}
      />
    </Stack.Navigator>
  );
};

export default NotAuthenticated;
udilevin commented 8 months ago

@nvacheishvili Can you please submit a support ticket to Pendo and we can schedule a zoom call?

nvacheishvili commented 8 months ago

sure, I'll look into that - thank you!

udilevin commented 8 months ago

@nvacheishvili I have a beta version for us to test once a zoom call is scheduled.

TehilaTaub commented 7 months ago

Hi @nvacheishvili, did you submit an internal ticket so we can instantiate a call to test the solution?

shlomipendo commented 6 months ago

Hey @nvacheishvili Closing this ticket due to inactivity. If a support ticket is filed, we can schedule a call to test the beta version.

vickyfuzstaffing commented 4 months ago

Hi @shlomipendo @udilevin is there any update on this ticket? I encountered the same thing

shlomipendo commented 4 months ago

@vickyfuzstaffing Hey Are you using our latest version (3.3.0 was released last week) I suggest checking if it reproduces with it, and if it is, please open a support ticket with all relevant details so our team can look into it.

vickyfuzstaffing commented 4 months ago

Hi @shlomipendo. Yes I encountered it when I try to migrate Pendo SDK from 2.22.2 to 3.3.0. It is reproducible, the onReady function would always throw a undefined error. How do I and where should I open a support ticket?

udilevin commented 4 months ago

https://support.pendo.io/hc/en-us/articles/360034163971-Get-help-with-Pendo-from-Technical-Support

orendayan commented 4 months ago

Solution found

0xkuj commented 4 months ago

The 'navigation' object hasn't been initialized yet, solution that was verified:

  1. Verify call WithPendoReactNavigation only once.
  2. Call WithPendoReactNavigation outside the AppMain function that they export. i.e outside of your rendering function
  3. Remove the old code from onReady -> const state = navigationRef.current.getRootState() props.onStateChange(state);