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
58 stars 2 forks source link

Error on app init - "State must not be null" #21

Closed SplinterStan closed 1 year ago

SplinterStan commented 1 year ago

Overview

Environment info:

Reproduction:

  1. First, we setup Pendo in our “index.js” via the “headlessCheck()” method:

    InitPendo
  2. Then, we wrap our NavigationContainer with “withPendoRN”:

withPendoRN
  1. And finally, we start a new Pendo session when the main navigation gets loaded (after user login):
PendoStartSession
  1. Unfortunately, when we run the app on iOS or Android, we get the following error:

    PendoStateError PendoAndroidError
  2. Still, the codeless event tracking seems to be working as we get some statistics on Pendo web app. Nevertheless, something seems to be going wrong there with the state.

Any help would be very much appreciated, thanks in advance!

MikePendo commented 1 year ago

@SplinterStan Strange, your integration seems to be fine. We display that error only in case u pass null in the state to the following method props.onStateChange(state); (in dev mode i.e u wont see this error in production as a result u will not get the analytics on that page). I think the best way would be to set a break point in the onStateChange and onReady in case state is null and see why and in which case the state is null.

MikePendo commented 1 year ago

@SplinterStan Amy progress about the issue OR a way to reproduce it?

SplinterStan commented 1 year ago

Hey @MikePendo, we tried to debug through and found out that onReady always returns undefined at navigationRef.current?.getRootState(), whereas onStateChange works fine returning a valid state (see attached screenshots):

onReady onStateChange
MikePendo commented 1 year ago

I would do the following:

  1. verify that _onStateChange_ get called on your first page (i.e u dont miss the analytics on the first page)
  2. In _onReady_ I would check if the state is null/undefined and dont call pendo (so u cant prevent this error).

In general we keeped the onReady specially for the first screen as explained here we noticed for some RN versions onStateChange want called for the first screen (but that also might be depend on the app structure)

SplinterStan commented 1 year ago

Hey,

  1. onStateChange seems to be called every time, even on the first screen. But we have 2 situations here:

-- if the user is already logged in (no Login screen + navigation to the main app navigation), then onStateChange always resolves successfully (no errors). -- if the user needs to login into the app first, there is the same error appearing inside onStateChange (see attached video). We are rendering two different navigators inside the NavigationContaier, dependending on if the user is logged in or not:

export function Navigation(): JSX.Element {
  // If sessionId is set, the user is already logged in, otherwise not.
  const { sessionId } = useSession();

  return (
    <View style={[tw.bgGray100, tw.flex1]}>
      {sessionId ? (
        <GraphQLProvider>
          <IntlWrapper>
            <ErrorBoundary>
              <CurrentUserProvider>
                <PermissionsProvider>
                  <ErrorProvider>
                    <BulkModeProvider>
                      <AlertsProvider>
                        <OnboardingProvider>
                          <StatusBar
                            barStyle="light-content"
                            backgroundColor={colors.brand}
                          />
                          <AuthenticatedAppView />
                          <ErrorSnackbar />
                        </OnboardingProvider>
                      </AlertsProvider>
                    </BulkModeProvider>
                  </ErrorProvider>
                </PermissionsProvider>
              </CurrentUserProvider>
            </ErrorBoundary>
          </IntlWrapper>
        </GraphQLProvider>
      ) : (
        <>
          <StatusBar barStyle="dark-content" />
          <Stack.Navigator screenOptions={{ headerShown: false }}>
            <Stack.Screen
              component={Login}
              name={MAIN_NAVIGATION_ITEMS.LOGIN}
            />
          </Stack.Navigator>
        </>
      )}
    </View>
  );
}

https://user-images.githubusercontent.com/46443337/203141874-59d81c2e-08bb-4283-8b5d-c630916ac5c0.mov

  1. We changed the code for onReady like the following and now it doesn't throw errors anymore:
onReady={() => {
        const state = navigationRef.current?.getRootState();
        state && props.onStateChange(state);
}}
MikePendo commented 1 year ago

ok, I have tried with react-navigation/native@6.0.6 react-navigation/stack@6.0.11 react-native@0.66.1 With some boolean value to differ between user login state (and I couldn't reproduce the error ) As far as I understand u have 2 options in your NavigatorContainer

  1. user have already looged in and the onStateChange get always called - all good
  2. user is not logged (u r showing login screen) onStateChange get called with null and u get the error. in this case does the onReady get called with valid state?

According to FB official docs the onStateChange will not be called on the first screen thats the reason why we use the onReady (we will basically queue the calls and will call the last one, so no analytics should be lost and if there is multiple analytics in very short period of time only the last one will be called). Another question what is actually null in (2) is it returned state OR navigationRef? Could u please update to navigation 6.0 and see if that works? If nothing helps I will need to have some minimal sample project that reproduce the issue (as in our sample project we don have such issues) I saw some similar issue with state get null here

MikePendo commented 1 year ago

@SplinterStan Any progress? did u manage to resole the issue?

SplinterStan commented 1 year ago

@MikePendo Sorry for the late answer. We did a lot of app restructuring to verify that we always have a rendered navigator at the root regardless of the current app internal navigation. It lead to some further issues after user login (endless re-rendering). While we were trying to fix that issue we found out that using the workaround for the onStateChange as we talked about before for onReady is the best option (rather than trying to find a "needle in a haystack" of our app component tree). We also confirmed that this workaround does track page visits on Pendo platform as well 🚀. Long story short, this is our "fix" for the issue:

onStateChange={() => {
        const state = navigationRef.current?.getRootState();
        state && props.onStateChange(state);
}}
onReady={() => {
        const state = navigationRef.current?.getRootState();
        state && props.onStateChange(state);
}}
MikePendo commented 1 year ago

@SplinterStan I am closing the issue feel free to reopen