Practice and reinforce the concepts from Lesson 12
Build scalable state management by:
Time Limit: 10 minutes
import React, { createContext, useContext, useReducer } from 'react';
const AppStateContext = createContext();
const AppDispatchContext = createContext();
const initialState = {
user: null,
theme: 'light',
notifications: [],
loading: false
};
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
default:
return state;
}
}
export function AppStateProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
}
export function useAppState() {
const context = useContext(AppStateContext);
if (!context) {
throw new Error('useAppState must be used within AppStateProvider');
}
return context;
}
export function useAppDispatch() {
const context = useContext(AppDispatchContext);
if (!context) {
throw new Error('useAppDispatch must be used within AppStateProvider');
}
return context;
}
✅ Checkpoint: Context provider wraps your app without errors!
Time Limit: 5 minutes
Use state in a component:
function ThemeToggle() {
const { theme } = useAppState();
const dispatch = useAppDispatch();
return (
<TouchableOpacity
onPress={() => dispatch({ type: 'TOGGLE_THEME' })}
style={[styles.button, { backgroundColor: theme === 'dark' ? '#333' : '#fff' }]}
>
<Text>Current theme: {theme}</Text>
</TouchableOpacity>
);
}
✅ Checkpoint: Theme toggle updates global state!
Build a social media app state manager:
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
const SocialAppContext = createContext();
const initialState = {
// User state
user: {
profile: null,
preferences: {
notifications: true,
darkMode: false,
language: 'en'
}
},
// Content state
posts: {
feed: [],
userPosts: [],
loading: false,
error: null,
pagination: {
page: 1,
hasMore: true
}
},
// Social state
social: {
following: [],
followers: [],
likes: new Set(),
bookmarks: new Set()
},
// UI state
ui: {
activeTab: 'home',
modalVisible: false,
searchQuery: '',
refreshing: false
}
};
function socialAppReducer(state, action) {
switch (action.type) {
case 'SET_USER_PROFILE':
return {
...state,
user: {
...state.user,
profile: action.payload
}
};
case 'LOAD_POSTS_START':
return {
...state,
posts: {
...state.posts,
loading: true,
error: null
}
};
case 'LOAD_POSTS_SUCCESS':
return {
...state,
posts: {
...state.posts,
loading: false,
feed: action.refresh
? action.payload
: [...state.posts.feed, ...action.payload],
pagination: {
page: action.refresh ? 2 : state.posts.pagination.page + 1,
hasMore: action.payload.length > 0
}
}
};
case 'TOGGLE_LIKE':
const newLikes = new Set(state.social.likes);
if (newLikes.has(action.payload)) {
newLikes.delete(action.payload);
} else {
newLikes.add(action.payload);
}
return {
...state,
social: {
...state.social,
likes: newLikes
}
};
case 'ADD_POST':
return {
...state,
posts: {
...state.posts,
feed: [action.payload, ...state.posts.feed],
userPosts: [action.payload, ...state.posts.userPosts]
}
};
case 'UPDATE_UI':
return {
...state,
ui: {
...state.ui,
...action.payload
}
};
default:
return state;
}
}
export function SocialAppProvider({ children }) {
const [state, dispatch] = useReducer(socialAppReducer, initialState);
// Persist user preferences
useEffect(() => {
const savePreferences = async () => {
try {
await AsyncStorage.setItem(
'userPreferences',
JSON.stringify(state.user.preferences)
);
} catch (error) {
console.log('Error saving preferences:', error);
}
};
if (state.user.profile) {
savePreferences();
}
}, [state.user.preferences]);
return (
<SocialAppContext.Provider value={{ state, dispatch }}>
{children}
</SocialAppContext.Provider>
);
}
export function useSocialApp() {
const context = useContext(SocialAppContext);
if (!context) {
throw new Error('useSocialApp must be used within SocialAppProvider');
}
return context;
}
Your Mission:
Create specialized hooks for better organization:
// Custom hooks for specific features
export function useUserState() {
const { state, dispatch } = useSocialApp();
const updateProfile = (profileData) => {
dispatch({ type: 'SET_USER_PROFILE', payload: profileData });
};
const updatePreferences = (prefs) => {
dispatch({
type: 'SET_USER_PROFILE',
payload: {
...state.user.profile,
preferences: { ...state.user.preferences, ...prefs }
}
});
};
return {
user: state.user,
updateProfile,
updatePreferences
};
}
export function usePostsState() {
const { state, dispatch } = useSocialApp();
const loadPosts = async (refresh = false) => {
dispatch({ type: 'LOAD_POSTS_START' });
try {
// Simulate API call
const posts = await fetchPosts(
refresh ? 1 : state.posts.pagination.page
);
dispatch({
type: 'LOAD_POSTS_SUCCESS',
payload: posts,
refresh
});
} catch (error) {
dispatch({
type: 'LOAD_POSTS_ERROR',
payload: error.message
});
}
};
const addPost = (postData) => {
const newPost = {
id: Date.now().toString(),
...postData,
createdAt: new Date().toISOString(),
likes: 0,
comments: []
};
dispatch({ type: 'ADD_POST', payload: newPost });
};
return {
posts: state.posts,
loadPosts,
addPost
};
}
export function useSocialActions() {
const { state, dispatch } = useSocialApp();
const toggleLike = (postId) => {
dispatch({ type: 'TOGGLE_LIKE', payload: postId });
// Optimistic update - sync with server later
updateLikeOnServer(postId, !state.social.likes.has(postId));
};
const followUser = (userId) => {
dispatch({
type: 'UPDATE_SOCIAL',
payload: {
following: [...state.social.following, userId]
}
});
};
return {
social: state.social,
toggleLike,
followUser
};
}
Optimize state updates for better performance:
import React, { memo, useMemo, useCallback } from 'react';
// Memoized components to prevent unnecessary re-renders
const PostItem = memo(({ post, onLike, isLiked }) => {
const handleLike = useCallback(() => {
onLike(post.id);
}, [post.id, onLike]);
return (
<View style={styles.postContainer}>
<Text>{post.content}</Text>
<TouchableOpacity
onPress={handleLike}
style={[styles.likeButton, isLiked && styles.liked]}
>
<Text>{isLiked ? '❤️' : '🤍'} {post.likes}</Text>
</TouchableOpacity>
</View>
);
});
// Optimized feed component
function PostFeed() {
const { posts, social } = useSocialApp();
const { toggleLike } = useSocialActions();
// Memoize expensive calculations
const likedPosts = useMemo(() => {
return new Set(social.likes);
}, [social.likes]);
// Memoize callback to prevent re-renders
const handleLike = useCallback((postId) => {
toggleLike(postId);
}, [toggleLike]);
const renderPost = useCallback(({ item }) => (
<PostItem
post={item}
onLike={handleLike}
isLiked={likedPosts.has(item.id)}
/>
), [handleLike, likedPosts]);
return (
<FlatList
data={posts.feed}
renderItem={renderPost}
keyExtractor={(item) => item.id}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
}
// State selector hook for fine-grained subscriptions
export function useStateSelector(selector) {
const { state } = useSocialApp();
return useMemo(() => selector(state), [selector, state]);
}
// Usage example
function UserProfile() {
const userProfile = useStateSelector(state => state.user.profile);
const postCount = useStateSelector(state => state.posts.userPosts.length);
return (
<View>
<Text>{userProfile?.name}</Text>
<Text>{postCount} posts</Text>
</View>
);
}
Completed Successfully If:
Time Investment: 60 minutes total Difficulty Level: Intermediate to Advanced Prerequisites: React hooks, Context API knowledge, async JavaScript Tools Needed: React Native development environment, AsyncStorage