Back to Blog
ReactStateHooksPerformance

React State Initialization Patterns

Master React state initialization with lazy initialization, derived state, and performance optimization.

B
Bootspring Team
Engineering
December 2, 2018
6 min read

Proper state initialization in React improves performance and prevents bugs. Here's how to do it effectively.

Basic Initialization#

1import { useState } from 'react'; 2 3function Counter() { 4 // Direct value initialization 5 const [count, setCount] = useState(0); 6 const [name, setName] = useState(''); 7 const [items, setItems] = useState([]); 8 const [user, setUser] = useState(null); 9 10 return <div>{count}</div>; 11}

Lazy Initialization#

1// For expensive computations, use a function 2function ExpensiveComponent({ data }) { 3 // This function only runs ONCE on mount 4 const [processed, setProcessed] = useState(() => { 5 return expensiveProcessing(data); 6 }); 7 8 return <div>{processed}</div>; 9} 10 11// BAD - runs on every render 12function BadExample({ data }) { 13 const [value, setValue] = useState(expensiveProcessing(data)); 14 return <div>{value}</div>; 15} 16 17// Reading from localStorage 18function ThemeProvider({ children }) { 19 const [theme, setTheme] = useState(() => { 20 const saved = localStorage.getItem('theme'); 21 return saved ? JSON.parse(saved) : 'light'; 22 }); 23 24 return ( 25 <ThemeContext.Provider value={{ theme, setTheme }}> 26 {children} 27 </ThemeContext.Provider> 28 ); 29}

Props to State (Anti-pattern Warning)#

1// AVOID - state that mirrors props 2function BadComponent({ initialValue }) { 3 // This only uses initialValue once, updates to prop are ignored 4 const [value, setValue] = useState(initialValue); 5 return <input value={value} onChange={e => setValue(e.target.value)} />; 6} 7 8// BETTER - use key to reset when needed 9function Parent() { 10 const [userId, setUserId] = useState(1); 11 12 return ( 13 // Key change resets component state 14 <UserForm key={userId} initialData={userData} /> 15 ); 16} 17 18// OR - use effect for sync (carefully) 19function SyncedComponent({ externalValue }) { 20 const [value, setValue] = useState(externalValue); 21 22 useEffect(() => { 23 setValue(externalValue); 24 }, [externalValue]); 25 26 return <input value={value} onChange={e => setValue(e.target.value)} />; 27}

Derived State#

1// DON'T store derived state 2function BadList({ items }) { 3 const [filteredItems, setFilteredItems] = useState([]); 4 const [filter, setFilter] = useState(''); 5 6 // Syncing derived state is error-prone 7 useEffect(() => { 8 setFilteredItems(items.filter(item => item.includes(filter))); 9 }, [items, filter]); 10 11 return /* ... */; 12} 13 14// DO compute during render 15function GoodList({ items }) { 16 const [filter, setFilter] = useState(''); 17 18 // Derived value - no state needed 19 const filteredItems = items.filter(item => item.includes(filter)); 20 21 // Use useMemo if expensive 22 const filteredItems = useMemo( 23 () => items.filter(item => item.includes(filter)), 24 [items, filter] 25 ); 26 27 return /* ... */; 28}

Complex State Objects#

1// Initialize with complete shape 2function Form() { 3 const [formData, setFormData] = useState({ 4 name: '', 5 email: '', 6 phone: '', 7 address: { 8 street: '', 9 city: '', 10 zip: '', 11 }, 12 }); 13 14 const updateField = (field, value) => { 15 setFormData(prev => ({ 16 ...prev, 17 [field]: value, 18 })); 19 }; 20 21 const updateAddress = (field, value) => { 22 setFormData(prev => ({ 23 ...prev, 24 address: { 25 ...prev.address, 26 [field]: value, 27 }, 28 })); 29 }; 30 31 return /* ... */; 32}

useReducer for Complex State#

1import { useReducer } from 'react'; 2 3const initialState = { 4 items: [], 5 loading: false, 6 error: null, 7 page: 1, 8}; 9 10function reducer(state, action) { 11 switch (action.type) { 12 case 'FETCH_START': 13 return { ...state, loading: true, error: null }; 14 case 'FETCH_SUCCESS': 15 return { ...state, loading: false, items: action.payload }; 16 case 'FETCH_ERROR': 17 return { ...state, loading: false, error: action.payload }; 18 case 'NEXT_PAGE': 19 return { ...state, page: state.page + 1 }; 20 case 'RESET': 21 return initialState; 22 default: 23 return state; 24 } 25} 26 27function DataList() { 28 const [state, dispatch] = useReducer(reducer, initialState); 29 30 // Or with lazy initialization 31 const [state, dispatch] = useReducer(reducer, null, () => { 32 return getInitialStateFromStorage() || initialState; 33 }); 34 35 return /* ... */; 36}

Ref for Non-Render Values#

1import { useState, useRef, useEffect } from 'react'; 2 3function Timer() { 4 const [count, setCount] = useState(0); 5 6 // Don't need state for values that don't affect render 7 const intervalRef = useRef(null); 8 const startTimeRef = useRef(null); 9 10 const start = () => { 11 startTimeRef.current = Date.now(); 12 intervalRef.current = setInterval(() => { 13 setCount(c => c + 1); 14 }, 1000); 15 }; 16 17 const stop = () => { 18 clearInterval(intervalRef.current); 19 console.log('Ran for:', Date.now() - startTimeRef.current); 20 }; 21 22 useEffect(() => { 23 return () => clearInterval(intervalRef.current); 24 }, []); 25 26 return ( 27 <div> 28 <p>{count}</p> 29 <button onClick={start}>Start</button> 30 <button onClick={stop}>Stop</button> 31 </div> 32 ); 33}

State Lifting#

1// Lift state to common ancestor 2function Parent() { 3 const [selectedId, setSelectedId] = useState(null); 4 5 return ( 6 <div> 7 <List items={items} selectedId={selectedId} onSelect={setSelectedId} /> 8 <Detail itemId={selectedId} /> 9 </div> 10 ); 11} 12 13function List({ items, selectedId, onSelect }) { 14 return ( 15 <ul> 16 {items.map(item => ( 17 <li 18 key={item.id} 19 onClick={() => onSelect(item.id)} 20 className={item.id === selectedId ? 'selected' : ''} 21 > 22 {item.name} 23 </li> 24 ))} 25 </ul> 26 ); 27}

Initial State from URL#

1function SearchPage() { 2 // Initialize from URL search params 3 const [searchParams, setSearchParams] = useSearchParams(); 4 5 const [filters, setFilters] = useState(() => ({ 6 query: searchParams.get('q') || '', 7 category: searchParams.get('cat') || 'all', 8 sort: searchParams.get('sort') || 'relevance', 9 })); 10 11 // Sync state back to URL 12 useEffect(() => { 13 const params = new URLSearchParams(); 14 if (filters.query) params.set('q', filters.query); 15 if (filters.category !== 'all') params.set('cat', filters.category); 16 if (filters.sort !== 'relevance') params.set('sort', filters.sort); 17 setSearchParams(params); 18 }, [filters, setSearchParams]); 19 20 return /* ... */; 21}

Reset State Pattern#

1function EditForm({ item, onSave, onCancel }) { 2 // Initialize from prop 3 const [draft, setDraft] = useState(item); 4 5 // Reset when item changes 6 useEffect(() => { 7 setDraft(item); 8 }, [item]); 9 10 // Or use key from parent 11 // <EditForm key={item.id} item={item} /> 12 13 const handleReset = () => { 14 setDraft(item); 15 }; 16 17 return ( 18 <form> 19 <input 20 value={draft.name} 21 onChange={e => setDraft({ ...draft, name: e.target.value })} 22 /> 23 <button type="button" onClick={handleReset}>Reset</button> 24 <button type="button" onClick={onCancel}>Cancel</button> 25 <button type="submit">Save</button> 26 </form> 27 ); 28}

Best Practices#

Initialization: ✓ Use lazy init for expensive ops ✓ Provide complete initial shape ✓ Use functions for localStorage ✓ Consider useReducer for complex Performance: ✓ Compute derived values, don't store ✓ Use refs for non-render values ✓ useMemo for expensive derivations ✓ Split state by update frequency Patterns: ✓ Lift state to common ancestor ✓ Use key to reset components ✓ Sync URL with state when needed ✓ Reset state explicitly when needed Avoid: ✗ Mirroring props in state ✗ Storing derived state ✗ Expensive inline initialization ✗ Unnecessary state synchronization

Conclusion#

Proper state initialization improves performance and prevents bugs. Use lazy initialization for expensive computations, compute derived values during render instead of storing them, and use refs for values that don't affect rendering. Lift state to the appropriate level and use the key prop to reset components when needed.

Share this article

Help spread the word about Bootspring