From b24f26fdd64035273df77f4082f97b048138e585 Mon Sep 17 00:00:00 2001 From: koniz-dev <20521498@gm.uit.edu.vn> Date: Fri, 14 Nov 2025 01:24:40 +0000 Subject: [PATCH 1/2] feat: integrate Redux Toolkit for state management - Added Redux Toolkit for managing application state, including authentication and todos. - Created authSlice and todosSlice for handling authentication and todos respectively. - Implemented typed hooks for accessing Redux store and dispatching actions. - Updated LoginScreen and ExploreScreen to utilize Redux for state management. - Enhanced README.md with details on Redux Toolkit implementation and state management options. - Introduced state-management-redux.md guide for comprehensive Redux Toolkit usage instructions. --- README.md | 20 + app/(auth)/login.tsx | 52 +- app/(tabs)/explore.tsx | 82 ++- app/_layout.tsx | 10 +- docs/state-management-redux.md | 630 +++++++++++++++++ package-lock.json | 1183 ++++++++++++++++++++++++++------ package.json | 5 +- store/hooks.ts | 35 + store/index.ts | 48 ++ store/slices/authSlice.ts | 223 ++++++ store/slices/todosSlice.ts | 253 +++++++ 11 files changed, 2290 insertions(+), 251 deletions(-) create mode 100644 docs/state-management-redux.md create mode 100644 store/hooks.ts create mode 100644 store/index.ts create mode 100644 store/slices/authSlice.ts create mode 100644 store/slices/todosSlice.ts diff --git a/README.md b/README.md index 1f654ee..1c6efa1 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ This starter includes everything you need to build a production-ready React Nati - ✅ **TypeScript** - Full type safety throughout - ✅ **ESLint + Prettier** - Code quality and formatting tools - ✅ **Example Screens** - See features in action +- ✅ **Redux Toolkit** - State management with TypeScript support (redux branch) ## Getting Started @@ -253,6 +254,24 @@ Expo Go is a free app for testing your app on physical devices: > **Note:** Expo Go is great for learning and prototyping, but not recommended for production apps. Use development builds instead. +## State Management Options + +This starter provides multiple state management implementations across different branches: + +- **main branch** - React hooks and Context API (built-in) +- **redux branch** - Redux Toolkit for complex state management +- **zustand branch** - Zustand for lightweight global state +- **react-context branch** - Advanced Context API patterns + +Choose the branch that fits your project needs: + +- **Use main branch** for simple apps with minimal shared state +- **Use redux branch** for complex apps requiring predictable state management, time-travel debugging, and team collaboration +- **Use zustand branch** for lightweight global state without Redux boilerplate +- **Use react-context branch** for apps that need Context API patterns with performance optimizations + +**📖 For Redux Toolkit implementation details, see [State Management with Redux Toolkit](docs/state-management-redux.md)** + ## Documentation ### Essential Guides @@ -267,6 +286,7 @@ Expo Go is a free app for testing your app on physical devices: - **[UI Library](docs/ui-library.md)** - React Native Paper components - **[Color Themes](docs/color-themes.md)** - Theming and dark mode - **[Error and Loading Handling](docs/error-and-loading.md)** - State management +- **[State Management with Redux Toolkit](docs/state-management-redux.md)** - Redux Toolkit guide (redux branch) ### Additional Guides diff --git a/app/(auth)/login.tsx b/app/(auth)/login.tsx index d0edc3a..88729c7 100644 --- a/app/(auth)/login.tsx +++ b/app/(auth)/login.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { View, StyleSheet, ScrollView } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { @@ -9,38 +9,46 @@ import { Snackbar, } from 'react-native-paper'; import { router } from 'expo-router'; -import { authService } from '@/services/auth'; +import { useAppDispatch, useAppSelector } from '@/store/hooks'; +import { loginAsync, selectAuth, clearError } from '@/store/slices/authSlice'; export default function LoginScreen() { const theme = useTheme(); + const dispatch = useAppDispatch(); + const { loading, error, isAuthenticated } = useAppSelector(selectAuth); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); const [snackbarVisible, setSnackbarVisible] = useState(false); - const handleLogin = async () => { + // Navigate to main app when authenticated + useEffect(() => { + if (isAuthenticated) { + router.replace('/(tabs)'); + } + }, [isAuthenticated]); + + // Show snackbar when error changes + useEffect(() => { + if (error) { + setSnackbarVisible(true); + } + }, [error]); + + const handleLogin = () => { if (!email || !password) { - setError('Please fill in all fields'); + dispatch(clearError()); setSnackbarVisible(true); + // For validation errors, we'll show a message directly + // In a real app, you might want to add validation to the slice return; } - setLoading(true); - setError(null); + dispatch(loginAsync({ email, password })); + }; - try { - await authService.login({ email, password }); - // Navigate to main app after successful login - router.replace('/(tabs)'); - } catch (err) { - const errorMessage = - err instanceof Error ? err.message : 'Login failed. Please try again.'; - setError(errorMessage); - setSnackbarVisible(true); - } finally { - setLoading(false); - } + const handleDismissSnackbar = () => { + setSnackbarVisible(false); + dispatch(clearError()); }; return ( @@ -109,11 +117,11 @@ export default function LoginScreen() { setSnackbarVisible(false)} + onDismiss={handleDismissSnackbar} duration={4000} action={{ label: 'Dismiss', - onPress: () => setSnackbarVisible(false), + onPress: handleDismissSnackbar, }} > {error || 'An error occurred'} diff --git a/app/(tabs)/explore.tsx b/app/(tabs)/explore.tsx index 26521bb..5ce2ec1 100644 --- a/app/(tabs)/explore.tsx +++ b/app/(tabs)/explore.tsx @@ -1,30 +1,52 @@ /** * Explore Screen * Example screen demonstrating API integration with loading, error, and success states - * Uses the useFetch hook for simplified data fetching + * + * This screen demonstrates Redux Toolkit for state management. + * For the useFetch hook approach, see the main branch. */ +import { useEffect } from 'react'; import { ScrollView, StyleSheet, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Card, Text, Button, useTheme } from 'react-native-paper'; -import { todosApi } from '@/services/api'; -import { useFetch } from '@/hooks/useFetch'; +import { useAppDispatch, useAppSelector } from '@/store/hooks'; +import { + fetchTodosAsync, + selectTodos, + selectTodosLoading, + selectTodosError, + toggleTodo, +} from '@/store/slices/todosSlice'; import { LoadingScreen } from '@/components/LoadingScreen'; -import type { Todo } from '@/types/api'; export default function ExploreScreen() { const theme = useTheme(); + const dispatch = useAppDispatch(); - // Use useFetch hook to simplify data fetching - const { - data: todos, - loading, - error, - refetch, - } = useFetch(async () => { - const data = await todosApi.getAll(); - // Limit to 10 todos for demo - return data.slice(0, 10); - }); + // Redux approach - Global state management + const reduxTodos = useAppSelector(selectTodos); + const reduxLoading = useAppSelector(selectTodosLoading); + const reduxError = useAppSelector(selectTodosError); + + // Fetch todos from Redux on mount + useEffect(() => { + if (reduxTodos.length === 0) { + dispatch(fetchTodosAsync()); + } + }, [dispatch, reduxTodos.length]); + + // Use Redux state for display + const todos = reduxTodos.slice(0, 10); // Limit to 10 for demo + const loading = reduxLoading; + const error = reduxError; + + const handleToggleTodo = (id: number) => { + dispatch(toggleTodo(id)); + }; + + const handleRefetch = () => { + dispatch(fetchTodosAsync()); + }; // Show full-screen loading on initial load if (loading && !todos && !error) { @@ -52,13 +74,19 @@ export default function ExploreScreen() { > Fetching todos from JSONPlaceholder API + + Using Redux Toolkit for state management + {/* Retry Button */}