Skip to main content

State management

In most real-world applications, you'll usually end up using a state management library instead of (or in addition to) managing your application state in context. Most popular state management libraries for React also support React Native. Some examples include:

I'm not advocating for any of these in particular and I encourage you to try them all out and choose which works for you when you start building your next app. But for this course, let's use Zustand.

Install the library

npx expo install zustand

Create a store

Create a new folder for store and add the userStore.ts with:

  1. hasFinishedOnboarding boolean to store whether the user has finished onboarding
  2. toggleHasOnboarded function to toggle the hasFinishedOnboarding state
New file: store/userStore.ts
import { create } from "zustand";

type UserState = {
hasFinishedOnboarding: boolean;
toggleHasOnboarded: () => void;
};

export const useUserStore = create<UserState>((set) => ({
hasFinishedOnboarding: false,
toggleHasOnboarded: () => {
set((state) => {
return {
...state,
hasFinishedOnboarding: !state.hasFinishedOnboarding,
};
});
},
}));

Read from the store

Now we can read the hasFinishedOnboarding from the Zustand store instead:

Update: app/(tabs)/_layout.tsx
@@ -2,10 +2,13 @@ import { Redirect, Tabs } from "expo-router";
import Entypo from "@expo/vector-icons/Entypo";
import Feather from "@expo/vector-icons/Feather";
import { theme } from "@/theme";
-
-const hasFinishedOnboarding = false;
+import { useUserStore } from "@/store/userStore";

export default function Layout() {
+ const hasFinishedOnboarding = useUserStore(
+ (state) => state.hasFinishedOnboarding,
+ );
+
if (!hasFinishedOnboarding) {
return <Redirect href="/onboarding" />;
}

Toggle the onboarding state from the onboarding modal and redirect to home:

Update: app/onboarding.tsx
@@ -1,10 +1,20 @@
-import { Text, View, StyleSheet } from "react-native";
+import { View, StyleSheet, Button } from "react-native";
import { theme } from "@/theme";
+import { useUserStore } from "@/store/userStore";
+import { useRouter } from "expo-router";

export default function OnboardingScreen() {
+ const router = useRouter();
+ const toggleHasOnboarded = useUserStore((state) => state.toggleHasOnboarded);
+
+ const handlePress = () => {
+ toggleHasOnboarded();
+ router.replace("/");
+ };
+
return (
<View style={styles.container}>
- <Text style={styles.text}>Onboarding</Text>
+ <Button title="Let me in" onPress={handlePress} />
</View>
);
}

Add a button to the profile page to toggle the onboarding state back to false:

Update: app/(tabs)/profile.tsx
@@ -1,10 +1,12 @@
-import { Text, View, StyleSheet } from "react-native";
+import { View, StyleSheet, Button } from "react-native";
import { theme } from "@/theme";
+import { useUserStore } from "@/store/userStore";

export default function ProfileScreen() {
+ const toggleHasOnboarded = useUserStore((store) => store.toggleHasOnboarded);
return (
<View style={styles.container}>
- <Text style={styles.text}>Profile</Text>
+ <Button title="Back to onboarding" onPress={toggleHasOnboarded} />
</View>
);
}
@@ -16,7 +18,4 @@ const styles = StyleSheet.create({
alignItems: "center",
backgroundColor: theme.colorWhite,
},
- text: {
- fontSize: 24,
- },
});

Finally update the (tabs) route animation to fade so that the app would fade in, instead of navigating as a stack.

Update: app/_layout.tsx
@@ -3,7 +3,10 @@ import { Stack } from "expo-router";
export default function RootLayout() {
return (
<Stack>
- <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
+ <Stack.Screen
+ name="(tabs)"
+ options={{ headerShown: false, animation: "fade" }}
+ />
<Stack.Screen
name="onboarding"
options={{
presentation: "modal",
headerShown: false,
+ animation: "fade",
}}
/>