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
Add unstable_transformImportMeta

From SDK 53 onwards you may need to add unstable_transformImportMeta to your babel config. Here is an example.

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",
}}
/>