useState & useReducer — Mobile State Patterns
State management starts with the right primitive. useState is perfect for independent values; useReducer shines when state transitions follow predictable rules or when multiple values change together. In mobile, also consider fetch state machines.
useState — Functional Updates & Complex State
import { useState } from 'react';
// ---- Simple state ----
const [count, setCount] = useState(0);
setCount(count + 1); // snapshot — may read stale value
// ✅ Functional update — always reads latest value
setCount(prev => prev + 1);
// ---- Object state — must spread (React does shallow compare) ----
interface UserSettings {
theme: 'light' | 'dark' | 'system';
fontSize: number;
pushEnabled: boolean;
}
const [settings, setSettings] = useState<UserSettings>({
theme: 'dark', fontSize: 14, pushEnabled: true,
});
// ✅ Partial update via spread
function updateTheme(theme: UserSettings['theme']) {
setSettings(prev => ({ ...prev, theme }));
}
// ---- Lazy initialization (avoid re-computing initial state) ----
// ❌ expensive — parseStoredSettings runs on EVERY render
const [data, setData] = useState(parseStoredSettings(rawJson));
// ✅ lazy — only runs once on mount
const [data2, setData2] = useState(() => parseStoredSettings(rawJson));
function parseStoredSettings(raw: string): UserSettings {
try { return JSON.parse(raw); } catch { return { theme: 'dark', fontSize: 14, pushEnabled: true }; }
}useReducer — Fetch State Machine
import { useReducer } from 'react';
// A fetch operation always moves through: idle → loading → success | error
// useReducer makes these transitions explicit and impossible to get into invalid states
type FetchState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
type FetchAction<T> =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; payload: T }
| { type: 'FETCH_ERROR'; error: Error }
| { type: 'RESET' };
function fetchReducer<T>(state: FetchState<T>, action: FetchAction<T>): FetchState<T> {
switch (action.type) {
case 'FETCH_START': return { status: 'loading' };
case 'FETCH_SUCCESS': return { status: 'success', data: action.payload };
case 'FETCH_ERROR': return { status: 'error', error: action.error };
case 'RESET': return { status: 'idle' };
default: return state;
}
}
function useProductDetail(productId: string) {
const [state, dispatch] = useReducer(fetchReducer<Product>, { status: 'idle' });
useEffect(() => {
if (!productId) return;
let cancelled = false;
dispatch({ type: 'FETCH_START' });
fetchProduct(productId)
.then(data => { if (!cancelled) dispatch({ type: 'FETCH_SUCCESS', payload: data }); })
.catch(error => { if (!cancelled) dispatch({ type: 'FETCH_ERROR', error }); });
return () => { cancelled = true; };
}, [productId]);
return state;
}
// Usage
function ProductDetail({ productId }: { productId: string }) {
const state = useProductDetail(productId);
if (state.status === 'loading') return <Loading />;
if (state.status === 'error') return <ErrorView error={state.error} />;
if (state.status === 'success') return <ProductCard product={state.data} />;
return null;
}
interface Product { id: string; name: string; }
async function fetchProduct(id: string): Promise<Product> { return { id, name: 'Product' }; }
function Loading() { return null; }
function ErrorView({ error }: { error: Error }) { return null; }
function ProductCard({ product }: { product: Product }) { return null; }Tip
Tip
Practice useState useReducer Mobile State Patterns in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
React Native bridges JavaScript and native platform code
Practice Task
Note
Practice Task — (1) Write a working example of useState useReducer Mobile State Patterns from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with useState useReducer Mobile State Patterns is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready react native code.