useState Deep Dive
The useState hook is the foundation of state management in React. Let's explore its advanced patterns and best practices.
Basic Syntax Revisited
jsx
1import { useState } from 'react'
2
3function Component() {
4 // Syntax: const [value, setValue] = useState(initialValue)
5 const [count, setCount] = useState(0)
6
7 return (
8 <button onClick={() => setCount(count + 1)}>
9 Count: {count}
10 </button>
11 )
12}Lazy Initialization
For expensive initial values, pass a function:
jsx
1// ❌ Runs on every render
2const [state, setState] = useState(expensiveCalculation())
3
4// ✅ Only runs once on mount
5const [state, setState] = useState(() => expensiveCalculation())
6
7// Real example
8function ExpensiveComponent() {
9 const [data, setData] = useState(() => {
10 const savedData = localStorage.getItem('myData')
11 return savedData ? JSON.parse(savedData) : { items: [], count: 0 }
12 })
13
14 return <div>{/* render data */}</div>
15}Functional Updates
When new state depends on previous state:
jsx
1function Counter() {
2 const [count, setCount] = useState(0)
3
4 // ❌ Can cause bugs with rapid updates
5 const handleClick = () => {
6 setCount(count + 1)
7 setCount(count + 1) // Still uses old count
8 // Result: count only increases by 1
9 }
10
11 // ✅ Correct - use functional update
12 const handleClick = () => {
13 setCount(prev => prev + 1)
14 setCount(prev => prev + 1)
15 // Result: count increases by 2
16 }
17}Complex State Updates
Objects
jsx
1function UserProfile() {
2 const [user, setUser] = useState({
3 name: 'John',
4 email: 'john@example.com',
5 preferences: {
6 theme: 'dark',
7 notifications: true
8 }
9 })
10
11 // Update single field
12 const updateName = (name) => {
13 setUser(prev => ({ ...prev, name }))
14 }
15
16 // Update nested field
17 const toggleTheme = () => {
18 setUser(prev => ({
19 ...prev,
20 preferences: {
21 ...prev.preferences,
22 theme: prev.preferences.theme === 'dark' ? 'light' : 'dark'
23 }
24 }))
25 }
26
27 return (
28 <div>
29 <input
30 value={user.name}
31 onChange={(e) => updateName(e.target.value)}
32 />
33 <button onClick={toggleTheme}>
34 Current theme: {user.preferences.theme}
35 </button>
36 </div>
37 )
38}Arrays
jsx
1function TodoApp() {
2 const [todos, setTodos] = useState([
3 { id: 1, text: 'Learn React', completed: false },
4 { id: 2, text: 'Build app', completed: false }
5 ])
6
7 // Add item
8 const addTodo = (text) => {
9 setTodos(prev => [...prev, {
10 id: Date.now(),
11 text,
12 completed: false
13 }])
14 }
15
16 // Remove item
17 const removeTodo = (id) => {
18 setTodos(prev => prev.filter(todo => todo.id !== id))
19 }
20
21 // Update item
22 const toggleTodo = (id) => {
23 setTodos(prev => prev.map(todo =>
24 todo.id === id
25 ? { ...todo, completed: !todo.completed }
26 : todo
27 ))
28 }
29
30 // Update specific field
31 const updateText = (id, text) => {
32 setTodos(prev => prev.map(todo =>
33 todo.id === id ? { ...todo, text } : todo
34 ))
35 }
36
37 // Insert at position
38 const insertAt = (index, newTodo) => {
39 setTodos(prev => [
40 ...prev.slice(0, index),
41 newTodo,
42 ...prev.slice(index)
43 ])
44 }
45
46 // Reorder items
47 const moveItem = (fromIndex, toIndex) => {
48 setTodos(prev => {
49 const result = [...prev]
50 const [removed] = result.splice(fromIndex, 1)
51 result.splice(toIndex, 0, removed)
52 return result
53 })
54 }
55}Multiple useState vs Single Object
jsx
1// Option 1: Multiple useState calls
2function Form() {
3 const [name, setName] = useState('')
4 const [email, setEmail] = useState('')
5 const [age, setAge] = useState(0)
6
7 // Easy to update individual fields
8 // Clear separation of concerns
9}
10
11// Option 2: Single object state
12function Form() {
13 const [form, setForm] = useState({
14 name: '',
15 email: '',
16 age: 0
17 })
18
19 const updateField = (field, value) => {
20 setForm(prev => ({ ...prev, [field]: value }))
21 }
22
23 // Easier to pass around, reset, or validate
24}
25
26// Recommendation:
27// - Use multiple useState for independent values
28// - Use single object for related values that update togetherReset State
jsx
1function ResettableForm() {
2 const initialState = {
3 name: '',
4 email: '',
5 message: ''
6 }
7
8 const [form, setForm] = useState(initialState)
9
10 const reset = () => {
11 setForm(initialState)
12 }
13
14 return (
15 <form>
16 {/* form fields */}
17 <button type="button" onClick={reset}>Reset</button>
18 </form>
19 )
20}State with TypeScript
tsx
1// Inferred types
2const [count, setCount] = useState(0) // number
3const [name, setName] = useState('') // string
4
5// Explicit types
6const [user, setUser] = useState<User | null>(null)
7const [items, setItems] = useState<Item[]>([])
8
9// Union types
10const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle')
11
12// Interface
13interface FormState {
14 name: string
15 email: string
16 age: number
17}
18
19const [form, setForm] = useState<FormState>({
20 name: '',
21 email: '',
22 age: 0
23})Common Patterns
Toggle Pattern
jsx
1function Toggle() {
2 const [isOn, setIsOn] = useState(false)
3
4 const toggle = () => setIsOn(prev => !prev)
5
6 return <button onClick={toggle}>{isOn ? 'ON' : 'OFF'}</button>
7}Counter Pattern
jsx
1function Counter() {
2 const [count, setCount] = useState(0)
3
4 const increment = () => setCount(prev => prev + 1)
5 const decrement = () => setCount(prev => prev - 1)
6 const reset = () => setCount(0)
7 const incrementBy = (n) => setCount(prev => prev + n)
8
9 return (
10 <div>
11 <span>{count}</span>
12 <button onClick={increment}>+</button>
13 <button onClick={decrement}>-</button>
14 <button onClick={reset}>Reset</button>
15 </div>
16 )
17}Input Pattern
jsx
1function Input() {
2 const [value, setValue] = useState('')
3
4 const handleChange = (e) => setValue(e.target.value)
5 const clear = () => setValue('')
6
7 return (
8 <div>
9 <input value={value} onChange={handleChange} />
10 <button onClick={clear}>Clear</button>
11 </div>
12 )
13}State Batching
React batches state updates for performance:
jsx
1function BatchingExample() {
2 const [count, setCount] = useState(0)
3 const [flag, setFlag] = useState(false)
4
5 const handleClick = () => {
6 // Both updates are batched - only one re-render
7 setCount(c => c + 1)
8 setFlag(f => !f)
9 }
10
11 console.log('Render') // Only logs once per click
12}Understanding useState deeply is crucial for effective React development!
