Skip
Arish's avatar

8. useState Deep Dive


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 together

Reset 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!