React Native – How to use Context API?

Structure an app Context

Create Context and Provider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// ContextProvider.tsx
export const AppContext = createContext({});
export const AppProvider: React.FC = ({ children, initData }) => {
const [user, setUser] = useState<IUser>({
accessToken: initData.accessToken || undefined,
idToken: initData.idToken || undefined,
refreshToken: initData.refreshToken || undefined,
});
return (
<AppContext.Provider
value={{
user,
updateUser: (data: IUser) => {
setUser(data);
},
}}
>
{children}
</AppContext.Provider>
);
};
export default AppProvider;
// ContextProvider.tsx export const AppContext = createContext({}); export const AppProvider: React.FC = ({ children, initData }) => { const [user, setUser] = useState<IUser>({ accessToken: initData.accessToken || undefined, idToken: initData.idToken || undefined, refreshToken: initData.refreshToken || undefined, }); return ( <AppContext.Provider value={{ user, updateUser: (data: IUser) => { setUser(data); }, }} > {children} </AppContext.Provider> ); }; export default AppProvider;
// ContextProvider.tsx

export const AppContext = createContext({});

export const AppProvider: React.FC = ({ children, initData }) => {
  const [user, setUser] = useState<IUser>({
    accessToken: initData.accessToken || undefined,
    idToken: initData.idToken || undefined,
    refreshToken: initData.refreshToken || undefined,
  });

  return (
    <AppContext.Provider
      value={{
        user,
        updateUser: (data: IUser) => {
          setUser(data);
        },
      }}
    >
      {children}
    </AppContext.Provider>
  );
};

export default AppProvider;
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// useAppContext.ts (custom hook)
const useAppContext = () => {
const appContext = useContext(AppContext);
const user = appContext?.user;
const updateUser = appContext?.updateUser;
// TODO: Continue to add context elements here
return { user, updateUser };
}
export default useAppContext;
// useAppContext.ts (custom hook) const useAppContext = () => { const appContext = useContext(AppContext); const user = appContext?.user; const updateUser = appContext?.updateUser; // TODO: Continue to add context elements here return { user, updateUser }; } export default useAppContext;
// useAppContext.ts (custom hook)

const useAppContext = () => {
    const appContext = useContext(AppContext);

    const user = appContext?.user;
    const updateUser = appContext?.updateUser;

    // TODO: Continue to add context elements here

    return { user, updateUser };
}

export default useAppContext;

Usage

parent component

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const App = () => {
return (
<ContextProvider initData={initData}>
<Routing />
</ContextProvider>
);
};
export default App;
const App = () => { return ( <ContextProvider initData={initData}> <Routing /> </ContextProvider> ); }; export default App;
const App = () => {
  return (
    <ContextProvider initData={initData}>
      <Routing />
    </ContextProvider>
  );
};

export default App;

child component

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const LOGOUT_STATUS = {
accessToken: '',
idToken: '',
refreshToken: '',
}
const Routing: React.FC = () => {
const { user, updateUser } = useAppContext();
useEffect(() => {
const checkAuth = async () => {
try {
const authUser = await getCurrentAuthUser();
if (authUser) {
updateUser?.(authUser);
} else {
updateUser?.(LOGOUT_STATUS);
}
} catch (error) {
updateUser?.(LOGOUT_STATUS);
}
};
checkAuth();
}, []);
return (
<NavigationContainer>
{user?.accessToken === undefined ? (
<StyledContainer>
<StyledSpinner />
</StyledContainer>
) : user?.accessToken ? (
<MainStack />
) : (
<LoginStack />
)}
</NavigationContainer>
);
};
export default Routing;
const LOGOUT_STATUS = { accessToken: '', idToken: '', refreshToken: '', } const Routing: React.FC = () => { const { user, updateUser } = useAppContext(); useEffect(() => { const checkAuth = async () => { try { const authUser = await getCurrentAuthUser(); if (authUser) { updateUser?.(authUser); } else { updateUser?.(LOGOUT_STATUS); } } catch (error) { updateUser?.(LOGOUT_STATUS); } }; checkAuth(); }, []); return ( <NavigationContainer> {user?.accessToken === undefined ? ( <StyledContainer> <StyledSpinner /> </StyledContainer> ) : user?.accessToken ? ( <MainStack /> ) : ( <LoginStack /> )} </NavigationContainer> ); }; export default Routing;
const LOGOUT_STATUS = {
    accessToken: '',
    idToken: '',
    refreshToken: '',
}

const Routing: React.FC = () => {
  const { user, updateUser } = useAppContext();

  useEffect(() => {
    const checkAuth = async () => {
      try {
        const authUser = await getCurrentAuthUser();
        if (authUser) {
          updateUser?.(authUser);
        } else {
          updateUser?.(LOGOUT_STATUS);
        }
      } catch (error) {
        updateUser?.(LOGOUT_STATUS);
      }
    };
    checkAuth();
  }, []);

  return (
    <NavigationContainer>
      {user?.accessToken === undefined ? (
        <StyledContainer>
          <StyledSpinner />
        </StyledContainer>
      ) : user?.accessToken ? (
        <MainStack />
      ) : (
        <LoginStack />
      )}
    </NavigationContainer>
  );
};

export default Routing;

How wrap just a few Screens with a context in React-navigation?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import Screen1 from '../pages/Screen1';
import Screen2 from '../pages/Screen2';
import { MyContextProvider } from '../providers/myContext';
const StackB = createStackNavigator();
export default function StackB() {
return (
<MyContextProvider>
<StackB.Navigator initialRouteName="Screen1">
<StackB.Screen name="Screen1" component={Screen1} />
<StackB.Screen name="Screen2" component={Screen2} />
</StackB.Navigator>
</MyContextProvider>
);
}
import React from 'react'; import {createStackNavigator} from '@react-navigation/stack'; import Screen1 from '../pages/Screen1'; import Screen2 from '../pages/Screen2'; import { MyContextProvider } from '../providers/myContext'; const StackB = createStackNavigator(); export default function StackB() { return ( <MyContextProvider> <StackB.Navigator initialRouteName="Screen1"> <StackB.Screen name="Screen1" component={Screen1} /> <StackB.Screen name="Screen2" component={Screen2} /> </StackB.Navigator> </MyContextProvider> ); }
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import Screen1 from '../pages/Screen1';
import Screen2 from '../pages/Screen2';
import { MyContextProvider } from '../providers/myContext';

const StackB = createStackNavigator();

export default function StackB() {
  return (
    <MyContextProvider>
      <StackB.Navigator initialRouteName="Screen1">
        <StackB.Screen name="Screen1" component={Screen1} />
        <StackB.Screen name="Screen2" component={Screen2} />
      </StackB.Navigator>
    </MyContextProvider>
  );
}
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import Screen3 from '../pages/Screen3';
import Screen4 from '../pages/Screen4';
import StackB from './StackB';
const Stack = createStackNavigator();
export default function MainRoutes() {
return (
<Stack.Navigator initialRouteName="StackB">
<Stack.Screen
name="StackB"
component={StackB}
options={{
headerShown: false,
}}
/>
<Stack.Screen name="Screen3" component={Screen3} />
<Stack.Screen name="Screen4" component={Screen4} />
</Stack.Navigator>
);
}
import React from 'react'; import {createStackNavigator} from '@react-navigation/stack'; import Screen3 from '../pages/Screen3'; import Screen4 from '../pages/Screen4'; import StackB from './StackB'; const Stack = createStackNavigator(); export default function MainRoutes() { return ( <Stack.Navigator initialRouteName="StackB"> <Stack.Screen name="StackB" component={StackB} options={{ headerShown: false, }} /> <Stack.Screen name="Screen3" component={Screen3} /> <Stack.Screen name="Screen4" component={Screen4} /> </Stack.Navigator> ); }
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import Screen3 from '../pages/Screen3';
import Screen4 from '../pages/Screen4';
import StackB from './StackB';

const Stack = createStackNavigator();

export default function MainRoutes() {
  return (
    <Stack.Navigator initialRouteName="StackB">
      <Stack.Screen
        name="StackB"
        component={StackB}
        options={{
          headerShown: false,
        }}
      />
      <Stack.Screen name="Screen3" component={Screen3} />
      <Stack.Screen name="Screen4" component={Screen4} />
    </Stack.Navigator>
  );
}

Issues

Value is updated multiple times in Context Provider

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const TabSettingsContent: React.FC<Props> = () => {
const context = useContext(SettingsContext)
...
// Fetch preference options
let preferenceItems: Array<TSettingsItem> = []
const { data, isLoading } = useQuery(
['fetchNotificationPreferences', email],
() => fetchNotificationPreferences(email),
{
enabled: !!email,
}
)
useEffect(() => {
if (!data) {
return
}
// Save necessary info into parent state
context.setSettings(data)
}, [context, data]) // !!!! ISSUE IS HERE: when new value is saved into context provider anywhere in the child component, it will trigger this useEffect to be run again. This makes old value "data" is saved into context again.
...
return ...
}
export default TabSettings
const TabSettingsContent: React.FC<Props> = () => { const context = useContext(SettingsContext) ... // Fetch preference options let preferenceItems: Array<TSettingsItem> = [] const { data, isLoading } = useQuery( ['fetchNotificationPreferences', email], () => fetchNotificationPreferences(email), { enabled: !!email, } ) useEffect(() => { if (!data) { return } // Save necessary info into parent state context.setSettings(data) }, [context, data]) // !!!! ISSUE IS HERE: when new value is saved into context provider anywhere in the child component, it will trigger this useEffect to be run again. This makes old value "data" is saved into context again. ... return ... } export default TabSettings
const TabSettingsContent: React.FC<Props> = () => {
  const context = useContext(SettingsContext)

  ...

  // Fetch preference options
  let preferenceItems: Array<TSettingsItem> = []
  const { data, isLoading } = useQuery(
    ['fetchNotificationPreferences', email],
    () => fetchNotificationPreferences(email),
    {
      enabled: !!email,
    }
  )

  useEffect(() => {
    if (!data) {
      return
    }

    // Save necessary info into parent state
    context.setSettings(data)
  }, [context, data]) // !!!! ISSUE IS HERE: when new value is saved into context provider anywhere in the child component, it will trigger this useEffect to be run again. This makes old value "data" is saved into context again.

  ...

  return ...
}

export default TabSettings
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const SettingsItem: React.FC<Props> = ({
title,
code,
isEnabled,
gridTemplateColumns,
}) => {
...
// Parent state
let input: TSettingsDataItem = undefined
const context = useContext(SettingsContext)
const settings = context?.settings
// Server state
const { refetch } = useQuery(
['updateNotificationPreferences', input],
() => updateNotificationPreferences(input),
{
enabled: false,
}
)
const onPressToggle = () => {
...
// Update parent state
input = getUpdateParam(settings, !isToggleOn, code)
context.setSettings(input) // THIS CHANGES "CONTEXT" WHICH TRIGGER useEffect IN PARENT COMPONENT
refetch()
}
return <>
<ToggleSwitch onPress={onPressToggle} />
</>
}
export default SettingsItem
const SettingsItem: React.FC<Props> = ({ title, code, isEnabled, gridTemplateColumns, }) => { ... // Parent state let input: TSettingsDataItem = undefined const context = useContext(SettingsContext) const settings = context?.settings // Server state const { refetch } = useQuery( ['updateNotificationPreferences', input], () => updateNotificationPreferences(input), { enabled: false, } ) const onPressToggle = () => { ... // Update parent state input = getUpdateParam(settings, !isToggleOn, code) context.setSettings(input) // THIS CHANGES "CONTEXT" WHICH TRIGGER useEffect IN PARENT COMPONENT refetch() } return <> <ToggleSwitch onPress={onPressToggle} /> </> } export default SettingsItem
const SettingsItem: React.FC<Props> = ({
  title,
  code,
  isEnabled,
  gridTemplateColumns,
}) => {
  ...

  // Parent state
  let input: TSettingsDataItem = undefined
  const context = useContext(SettingsContext)
  const settings = context?.settings

  // Server state
  const { refetch } = useQuery(
    ['updateNotificationPreferences', input],
    () => updateNotificationPreferences(input),
    {
      enabled: false,
    }
  )

  const onPressToggle = () => {
    ...
    
    // Update parent state
    input = getUpdateParam(settings, !isToggleOn, code)
    context.setSettings(input) // THIS CHANGES "CONTEXT" WHICH TRIGGER useEffect IN PARENT COMPONENT

    refetch()
  }

  return <>
    <ToggleSwitch onPress={onPressToggle} />
  </>
}

export default SettingsItem

Update value 5 times by loop “for”, UI is updated with first time value only

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
...
const [count, updateCount] = React.useState(0);
...
return (
<AppContext.Provider
value={{
count: count,
updateCount: () => {
updateCount(count + 1);
},
}}
>
{children}
</AppContext.Provider>
);
...
... const [count, updateCount] = React.useState(0); ... return ( <AppContext.Provider value={{ count: count, updateCount: () => { updateCount(count + 1); }, }} > {children} </AppContext.Provider> ); ...
...
const [count, updateCount] = React.useState(0);
...
return (
    <AppContext.Provider
      value={{
        count: count,
        updateCount: () => {
          updateCount(count + 1);
        },
      }}
    >
      {children}
    </AppContext.Provider>
  );
...
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const moduleState = React.useContext(AppContext);
...
for (let i = 1; i <= 5; i++) {
moduleState.updateCount();
}
// Expect UI to be displayed with 5 but UI displays 1
...
const moduleState = React.useContext(AppContext); ... for (let i = 1; i <= 5; i++) { moduleState.updateCount(); } // Expect UI to be displayed with 5 but UI displays 1 ...
const moduleState = React.useContext(AppContext);
...
for (let i = 1; i <= 5; i++) {
  moduleState.updateCount();
}

// Expect UI to be displayed with 5 but UI displays 1
...

This may be because “moduleState” is still old, so previous value is always the same, not to be incremented

Be the first to comment

Leave a Reply

Your email address will not be published.


*