Search code examples
javascriptreactjsreact-nativereact-navigationreact-context

How to use Context for a drawer screen and its sub screen / components?


Issue: I have a context wrapped around my entire app but I need an additional context for a single drawer screen and its sub screens & components.

DataProvider is used by the entire App.

I have created a context to be used solely by the 'component' A I.e Screen A in the Drawer Navigator and its children(A sub screen) in Stack Navigator.

What has been done thus far:

Read docs and proceeded to wrap the entirety of the app with both context but considering just a subset of the drawer navigator and screen actually needs the AContext, I need to wrap just the screens / components that only needs it to prevent unnecessary re-renders for screens and components that doesn't require the context in the first place.

Any suggestion on how to proceed in this case or is it negligible?

Here's my App.js code:

const DrawerNavigator = () => {
  
  return (
    <Drawer.Navigator
      initialRouteName=
      screenOptions={({ navigation }) => ({
        headerTitle: "",
      }
      )}
    >
      <Drawer.Screen
        name="A"
        component={A}
        options={{
          // freezeOnBlur: true,
        }}
      />
      <Drawer.Screen
        name="B"
        component={B}
        options={{
          freezeOnBlur: true,
        }}
      />
      <Drawer.Screen
        name="C"
        component={C}
        options={{
          freezeOnBlur: true,
        }}
      />
    </Drawer.Navigator>
  )
};

export default function App() {
  return (
    <>
      <DataProvider>
        <AProvider>
          <NavigationContainer>
            <Stack.Navigator
              screenOptions={({ navigation }) => ({
                headerTitle: "",
              })}
            >
              <Stack.Screen
                name="HOME"
                component={DrawerNavigator}
                options={{ headerShown: false }}
              />
              <Stack.Screen
                name="ASubScreen"
                component={ASubScreen}
                options={{
              />
              <Stack.Screen
                name="D"
                component={D}
                options={{ headerTitle: "" }}
              />
              <Stack.Screen
                name="E"
                component={E}
                options={{ headerTitle: "" }}
              />Ï
            </Stack.Navigator>
          </NavigationContainer>
          <ConsentModal />
        </AProvider>
      </DataProvider>
      <StatusBar style="dark" />
      <Toast />
    </>
  );
}

Here's the context code:

import React, { useEffect, useState } from "react";

export const AContext = React.createContext();

export const PayEProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);

  return (
    <AContext.Provider
      value={{
        currentUser,
        setCurrentUser
      }}
    >
      {children}
    </AContext.Provider>
  );
};

Solution

  • Depending on what the AContext value is or what the AProvider provider component needs to handle and update it may be negligible, but it's always a good coding practice from a design/implementation to limit the scope of variables as much as possible. In other words, only lift the state/context/etc as high up the app structure as is necesssary.

    With React-Navigation Screen components I believe you have a couple basic options:

    • Create a screen component that renders the Context provider and the actual screen component, all on the component prop.

      const ASubScreenLayout = (props) => {
        ...
      
        return (
          <AProvider>
            ...
            <ASubScreen {...props} />
            ...
          </AProvider>
        );
      };
      
      export default function App() {
        return (
          <>
            <DataProvider>
              <NavigationContainer>
                <Stack.Navigator
                  screenOptions={({ navigation }) => ({
                    headerTitle: "",
                  })}
                >
                  ...
                  <Stack.Screen
                    name="ASubScreen"
                    component={ASubScreenLayout}
                    options={{ ... }}
                  />
                  ...
                </Stack.Navigator>
              </NavigationContainer>
              <ConsentModal />
            </DataProvider>
      
            <StatusBar style="dark" />
            <Toast />
          </>
        );
      }
      
    • Use the children render prop function, which is basically the above solution just inlined.

      export default function App() {
        return (
          <>
            <DataProvider>
              <NavigationContainer>
                <Stack.Navigator
                  screenOptions={({ navigation }) => ({
                    headerTitle: "",
                  })}
                >
                  ...
                  <Stack.Screen
                    name="ASubScreen"
                    options={{ ... }}
                  >
                    {(props) => (
                      <AProvider>
                        <ASubScreen {...props} />
                      </AProvider>
                    )}
                  </Stack.Screen>
                  ...
                </Stack.Navigator>
              </NavigationContainer>
              <ConsentModal />
            </DataProvider>
      
            <StatusBar style="dark" />
            <Toast />
          </>
        );
      }