Now that you understand what state is, it's time to learn how to actually use it! The useState hook is React's primary way to add state to function components. It's simple, powerful, and absolutely essential for building interactive UIs. In this lesson, you'll learn how to create state, update it, and handle different data types - making your components truly dynamic and responsive.
Basic useState Syntax
Here's the fundamental pattern for using useState:
import React from 'react'
function Counter() {
// Create state with useState
const [count, setCount] = React.useState(0)
// ↑ ↑ ↑
// current setter initial value
// value function
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
)
}
// When you click the button:
// 1. setCount(count + 1) is called
// 2. React re-renders the component
// 3. count now has the new value
// 4. UI updates to show new countBreaking Down useState:
React.useState(initialValue)- Creates a state variable- Returns an array with 2 elements:
[value, setter] count- The current state value (read it in your JSX)setCount- Function to update the state0- The initial value (only used on first render)
Your First useState Example
Let's build a working counter step by step:
function Counter() {
// Step 1: Create state
const [count, setCount] = React.useState(0)
// Step 2: Create handler function
const increment = () => {
setCount(count + 1) // Update state
}
const decrement = () => {
setCount(count - 1) // Update state
}
const reset = () => {
setCount(0) // Reset to initial value
}
// Step 3: Use state in JSX
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={reset}>Reset</button>
</div>
)
}💡 Naming Convention
State variables are typically named with a descriptive noun:count, name, isOpen
Setter functions are named with "set" + capitalized variable name:setCount, setName, setIsOpen
Different State Types
1. Number State
function Counter() {
const [count, setCount] = React.useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(count * 2)}>×2</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
)
}2. String State
function NameInput() {
const [name, setName] = React.useState('')
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<p>Hello, {name || 'stranger'}!</p>
</div>
)
}3. Boolean State
function Toggle() {
const [isOn, setIsOn] = React.useState(false)
return (
<div>
<p>The light is {isOn ? 'ON' : 'OFF'}</p>
<button onClick={() => setIsOn(!isOn)}>
Toggle
</button>
<button onClick={() => setIsOn(true)}>
Turn ON
</button>
<button onClick={() => setIsOn(false)}>
Turn OFF
</button>
</div>
)
}4. Array State
function TodoList() {
const [todos, setTodos] = React.useState([])
const [input, setInput] = React.useState('')
const addTodo = () => {
if (input.trim()) {
setTodos([...todos, input]) // Add to array
setInput('') // Clear input
}
}
const removeTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index)) // Remove from array
}
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo"
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => removeTodo(index)}>Delete</button>
</li>
))}
</ul>
</div>
)
}5. Object State
function UserForm() {
const [user, setUser] = React.useState({
name: '',
email: '',
age: 0
})
const updateName = (e) => {
setUser({ ...user, name: e.target.value })
}
const updateEmail = (e) => {
setUser({ ...user, email: e.target.value })
}
const updateAge = (e) => {
setUser({ ...user, age: parseInt(e.target.value) })
}
return (
<div>
<input
type="text"
value={user.name}
onChange={updateName}
placeholder="Name"
/>
<input
type="email"
value={user.email}
onChange={updateEmail}
placeholder="Email"
/>
<input
type="number"
value={user.age}
onChange={updateAge}
placeholder="Age"
/>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
)
}Important: State is Immutable!
Never modify state directly:
❌ todos.push(newTodo)
❌ user.name = 'Alice'
Always create a new value:
✅ setTodos([...todos, newTodo])
✅ setUser({ ...user, name: 'Alice' })
Updating State
Direct Value Updates
function Example() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('Alice')
const [isOpen, setIsOpen] = React.useState(false)
// Direct value - pass the new value
setCount(5) // Sets count to 5
setName('Bob') // Sets name to 'Bob'
setIsOpen(true) // Sets isOpen to true
// Based on current value
setCount(count + 1) // Increment count
setName('') // Clear name
setIsOpen(!isOpen) // Toggle isOpen
}Function Updates (Recommended for Previous State)
function Counter() {
const [count, setCount] = React.useState(0)
// ❌ Not reliable for multiple updates
const increment = () => {
setCount(count + 1)
setCount(count + 1) // Still uses old count!
setCount(count + 1) // Still old count!
// Result: count increases by 1, not 3
}
// ✅ Reliable - uses previous state
const increment = () => {
setCount(prev => prev + 1) // prev is the actual current value
setCount(prev => prev + 1) // prev is now updated
setCount(prev => prev + 1) // prev is updated again
// Result: count increases by 3
}
return <button onClick={increment}>Count: {count}</button>
}🎯 When to Use Function Updates
Use function form setState(prev => ...) when:
• The new state depends on the previous state
• You're updating state multiple times
• You're in a loop or async function
Use direct form setState(value) when:
• Setting a completely new value
• The new value doesn't depend on old value
Common useState Patterns
1. Toggle Pattern
function Modal() {
const [isOpen, setIsOpen] = React.useState(false)
const toggle = () => setIsOpen(!isOpen)
const open = () => setIsOpen(true)
const close = () => setIsOpen(false)
return (
<div>
<button onClick={open}>Open Modal</button>
{isOpen && (
<div className="modal">
<div className="modal-content">
<h2>Modal Content</h2>
<button onClick={close}>Close</button>
</div>
</div>
)}
</div>
)
}2. Input Binding Pattern
function SearchBox() {
const [query, setQuery] = React.useState('')
const handleSearch = () => {
console.log('Searching for:', query)
}
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<button onClick={handleSearch}>Search</button>
<button onClick={() => setQuery('')}>Clear</button>
</div>
)
}3. Counter Pattern
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount(prev => prev + 1)
const decrement = () => setCount(prev => prev - 1)
const reset = () => setCount(0)
const incrementBy = (amount) => setCount(prev => prev + amount)
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={() => incrementBy(5)}>+5</button>
<button onClick={reset}>Reset</button>
</div>
)
}4. List Management Pattern
function ItemList() {
const [items, setItems] = React.useState(['Item 1', 'Item 2'])
const addItem = () => {
const newItem = `Item ${items.length + 1}`
setItems([...items, newItem])
}
const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index))
}
const clearAll = () => {
setItems([])
}
return (
<div>
<button onClick={addItem}>Add Item</button>
<button onClick={clearAll}>Clear All</button>
<ul>
{items.map((item, index) => (
<li key={index}>
{item}
<button onClick={() => removeItem(index)}>×</button>
</li>
))}
</ul>
</div>
)
}5. Form State Pattern
function LoginForm() {
const [formData, setFormData] = React.useState({
email: '',
password: '',
rememberMe: false
})
const handleChange = (e) => {
const { name, value, type, checked } = e.target
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : value
})
}
const handleSubmit = (e) => {
e.preventDefault()
console.log('Form data:', formData)
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
<label>
<input
type="checkbox"
name="rememberMe"
checked={formData.rememberMe}
onChange={handleChange}
/>
Remember me
</label>
<button type="submit">Login</button>
</form>
)
}Interactive Demo
💡 Tip: Edit the code above and click "Run" to see your changes
Common useState Mistakes
1. Mutating State Directly
function TodoList() {
const [todos, setTodos] = React.useState([])
// ❌ Wrong - mutates state
const addTodo = (text) => {
todos.push(text)
setTodos(todos) // React won't re-render!
}
// ✅ Correct - creates new array
const addTodo = (text) => {
setTodos([...todos, text])
}
}2. Using State Immediately After Setting
function Counter() {
const [count, setCount] = React.useState(0)
// ❌ Wrong - count won't be updated yet
const handleClick = () => {
setCount(count + 1)
console.log(count) // Still shows old value!
}
// ✅ Correct - use the value you're setting
const handleClick = () => {
const newCount = count + 1
setCount(newCount)
console.log(newCount) // Shows new value
}
}3. Calling Setter Function Incorrectly
function Example() {
const [count, setCount] = React.useState(0)
// ❌ Wrong - calling immediately (infinite loop!)
return <button onClick={setCount(count + 1)}>Click</button>
// ✅ Correct - pass a function
return <button onClick={() => setCount(count + 1)}>Click</button>
// ✅ Also correct - reference function
const increment = () => setCount(count + 1)
return <button onClick={increment}>Click</button>
}4. Forgetting Dependencies
function Counter() {
const [count, setCount] = React.useState(0)
// ❌ Using stale count value
const incrementTwice = () => {
setCount(count + 1)
setCount(count + 1) // Uses same old count!
// Result: increments by 1, not 2
}
// ✅ Use function form for previous state
const incrementTwice = () => {
setCount(prev => prev + 1)
setCount(prev => prev + 1)
// Result: increments by 2
}
}5. Initializing with Expensive Computation
// ❌ Runs on every render (slow!)
const [data, setData] = React.useState(expensiveCalculation())
// ✅ Only runs once (fast!)
const [data, setData] = React.useState(() => expensiveCalculation())
// The function form ensures it only runs onceKey Takeaways
useState(initialValue)creates state in function components- Returns array:
[value, setter] - Setter function triggers re-renders
- Use
setState(newValue)for direct updates - Use
setState(prev => newValue)when new state depends on previous - Never mutate state directly - always create new values
- State updates are asynchronous
- Works with any data type: primitives, objects, arrays
- For objects/arrays, use spread operator to create copies
- Name state variables descriptively (count, name, isOpen)
- Name setters with "set" prefix (setCount, setName, setIsOpen)
- Each component instance has its own state
- Initial value only used on first render
What's Next?
You now know how to use the useState hook to add state to your components! You can create interactive counters, forms, toggles, and more.
But there's more to understand about how state works. In the next lesson, you'll learn about state updates and re-renders:
- How React decides when to re-render
- What happens during a re-render
- Why state updates are batched
- How to optimize re-renders
- Common re-rendering pitfalls
Understanding re-renders will help you write more efficient React applications! 🚀