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:
hasFinishedOnboarding
boolean to store whether the user has finished onboardingtoggleHasOnboarded
function to toggle thehasFinishedOnboarding
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",
}}
/>