Structure an app Context
Create Context and Provider
// 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;
// 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
const App = () => { return ( <ContextProvider initData={initData}> <Routing /> </ContextProvider> ); }; export default App;
child component
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?
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 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
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 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
... const [count, updateCount] = React.useState(0); ... return ( <AppContext.Provider value={{ count: count, updateCount: () => { updateCount(count + 1); }, }} > {children} </AppContext.Provider> ); ...
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
Leave a Reply