Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React (Native) Context API Causes Stack Navigator (React Navigation 5) to re-render after state update

Thanks in advance for helping with this question.


Some Context (No Pun Intended)

I'm working on a company's project that will restrict me from putting all the code on here, but I will try and put as much relevant code as I can without revealing state secrets.

Basically, the React Native app is using React Navigation v5 and I've been trying to figure this problem out for two days. I understand what is happening, but I don't understand why its happening or how to fix it.

Somewhere inside the app code, you have the below code.

export const Lounge = function Lounge(props: {navigation: any}) {
  
  const {navigation: {setOptions}} = props
  const bottomTabBarCtx = useContext(BottomTabBarContext)

  // const [routeName, setRouteName] = useState("LoungeList")

  useEffect(() => {
    setOptions({tabBarVisible: bottomTabBarCtx.isVisible})
  }, [bottomTabBarCtx.isVisible, setOptions])

  return <Stack.Navigator initialRouteName={"LoungeList"}>
    <Stack.Screen
      name="LoungeList"
      component={List}
      options={{headerShown: false}}
    />

    <Stack.Screen
      name="LoungeDetails"
      component={Details}
      options={{
        headerLeft : buttonProps => <BackButton {...buttonProps} />,
        headerRight: buttonProps => <LoungeHeaderRightDetailsButton {...buttonProps} />,
        headerTitle: 'Lounge Details',
      }}
    />

    <Stack.Screen
      name="LoungeParticipants"
      component={Participants}

      options={{
        headerLeft : buttonProps => <BackButton {...buttonProps} />,
        headerRight: buttonProps => <LoungeHeaderRightDetailsButton {...buttonProps} />,
        headerTitle: 'Lounge Participants',
      }}
    />

    <Stack.Screen
      name="LoungeAdd"
      component={LoungeEdit}

      options={{
        headerLeft : buttonProps => <BackButton {...buttonProps} />,
        headerTitle: 'Create Lounge',
      }}
    />

    <Stack.Screen
      name="LoungeEdit"
      component={LoungeEdit}

      options={{
        headerLeft : buttonProps => <BackButton {...buttonProps} />,
        headerTitle: 'Edit Lounge',
      }}
    />

...

The above file simply defines the routes that can be navigated to in this section of the app.

The initial screen/route name = 'LoungeList'

That all works fine.

From the LoungeList screen, I tap on a lounge summary cell (use your imagination please), and that tap takes me to the screen underneath: LoungeDetails

Inside the LoungeDetails screen, I see the details of the lounge, and on that screen, there is also a Join Button. This button enables me to join the lounge.

The functionality is supposed to be that, when a user taps on the join button, the button goes into a loading state, and when it has finished loading, it should read Joined. This happens as it should BUT immediately after this happens I am navigated to the LoungeList screen

Below is the code I execute for joining a lounge:

const userDidJoinLounge = (joinedLounge: LoungeWithUsers) => {

    if(joinedLounge){

      console.log("joinedLounge before update == ", joinedLounge);
      
      const allLoungesNew = LoungeController.replaceLoungeInLoungeArray(
        userCtx.allLoungesList,
        joinedLounge
      );

      const userLoungesNew = LoungeController.getUserJoinedLoungeArrayListOnJoin(
        allLoungesNew,
        userCtx.userLoungeList,
        joinedLounge
      );

      console.log("allLounges after update == ", allLoungesNew);
      console.log("joinedLounges after update == ", userLoungesNew);

      userCtx.updateLoungeLists(allLoungesNew, userLoungesNew)

    }
  }

In the above function, the line

userCtx.updateLoungeLists(allLoungesNew, userLoungesNew) takes in two parameters, a list of all the lounges on the app, and a list of lounges the user has joined. This function updates the context that holds these two values.


THE BIG ISSUE

Issue: I keep getting redirected to the LoungeList screen, even though there isn't any code instructing the app to do that.

THINGS THAT MIGHT HELP

I've played around with many things and a few things that might help you in figuring out what is going on are:

  • It seems that the React Navigation route config is being re-rendered. I say this because when I changed the initialRouteName to be LoungeDetails, the lounge details screen (where the join button is) flickers, but it stays on the same screen. This also tells me, that at the end of that function, the stack navigator falls back to whatever initialRouteName is.

  • Wierdly, when I only set allLoungesNew then the app doesn't navigate to initialRouteName, it seems this issue only happens when I set userLoungesNew in the userContext's state.

How do I stop react-navigation from navigating away from the screen when the user has tapped the join button?

Thank you very much for your help. I hope I've given you enough information to solve the problem. Feel free to ask any more questions to help solve the problem. I will tell you what I am allowed to.

like image 273
Ini Jed Elliot Atoyebi Avatar asked Jul 31 '20 17:07

Ini Jed Elliot Atoyebi


1 Answers

After about 3 days of searching the internet for an answer, the answer was pretty simple.

I had found numerous articles and suggestions telling me not to put any code for creating navigators inside the component itself.

I didnt write the original code for this app, I came on about 2 months ago, and while I had looked at the nested navigators, It didn’t immediately occur to me that the problem might be higher up the navigation chain/stack.

In the root index.tsx file, the former developers had put the root navigator (the navigator that held all the other sub/nested navigators) inside the entry point component.

This file was the only place that any navigator was being created within the component, but I didn’t think to look there because the behaviour seemed localised to the lounge section of the app

The Solution

Anyway, the fix was simple. In the root index.tsx file, I moved every navigator declaration out of the (root) functional component, and that fixed the issue.

It was clear that the navigator was being unmourned/remounted whenever I called a state change on a state held in my UserContext, it makes sense now that because the code to create the navigators was inside a component, whenever I would change the state, the navigators would be re-rendered as well, which took me back to the initialRouteName screen.

Main Lesson Learned

React Navigation v5 unlike its earlier versions now allows us to declare navigators with JSX tags, which gives us more flexibility by providing the opportunity for things like dynamic configurations of our navigators, however we have to be careful about where any/all of our navigators are declared

After 3 days on this problem, everything pointed to this solution, but I didn’t think that was the case in the code I was working on because with the exception of one file, all other navigator declarations were made outside the component.

It could be the same for you. If you’re getting that kind of behaviour when your state changes, then the problem navigator may be way up your chain of navigators.

I hope this helps someone! 😊

like image 69
Ini Jed Elliot Atoyebi Avatar answered Sep 28 '22 05:09

Ini Jed Elliot Atoyebi