Understanding State in React
State is one of the most powerful features in React. It allows components to create and manage their own data, making applications interactive and dynamic. When state changes, React automatically re-renders the component to reflect the new data.
Key Concept: State is private and fully controlled by the component. Unlike props which are passed from parent to child, state is managed within the component itself.
The useState Hook
The useState hook is the primary way to add state to functional components. It returns an array with two elements: the current state value and a function to update it.
import React, { useState } from 'react';
function Counter() {
// Declare a state variable named "count" with initial value 0
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Naming Convention: When using array destructuring with useState, name your state variable descriptively and prefix the setter function with "set". For example: [user, setUser], [isLoading, setIsLoading].
How useState Works
Let's break down the useState syntax:
- Initial State: The argument passed to
useState(0) is the initial state value
- Current State: The first array element (
count) is the current state value
- State Setter: The second array element (
setCount) is a function to update the state
function Example() {
// Multiple state variables in one component
const [age, setAge] = useState(25);
const [name, setName] = useState('John');
const [isActive, setIsActive] = useState(true);
return (
<div>
<p>Name: {name}, Age: {age}</p>
<p>Status: {isActive ? 'Active' : 'Inactive'}</p>
<button onClick={() => setAge(age + 1)}>
Increase Age
</button>
<button onClick={() => setIsActive(!isActive)}>
Toggle Status
</button>
</div>
);
}
Updating State with Objects
When state is an object, you need to be careful to preserve existing properties when updating. React doesn't merge object state automatically - it replaces it.
function UserProfile() {
const [user, setUser] = useState({
name: 'Alice',
age: 28,
email: 'alice@example.com'
});
const updateName = (newName) => {
// WRONG: This will lose age and email
// setUser({ name: newName });
// CORRECT: Spread existing properties
setUser({
...user,
name: newName
});
};
const updateAge = () => {
setUser(prevUser => ({
...prevUser,
age: prevUser.age + 1
}));
};
return (
<div>
<h3>{user.name}</h3>
<p>Age: {user.age}</p>
<p>Email: {user.email}</p>
<button onClick={() => updateName('Bob')}>
Change Name
</button>
<button onClick={updateAge}>
Increase Age
</button>
</div>
);
}
Common Mistake: Never mutate state directly! Always use the setter function. Writing count = count + 1 or user.name = 'Bob' will not trigger a re-render and breaks React's state management.
Updating State with Arrays
Similar to objects, when updating array state, create a new array instead of mutating the existing one.
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
// Add new todo to the end
setTodos([...todos, { id: Date.now(), text: input }]);
setInput('');
}
};
const removeTodo = (id) => {
// Filter out the todo with matching id
setTodos(todos.filter(todo => todo.id !== id));
};
const clearAll = () => {
setTodos([]);
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add a todo"
/>
<button onClick={addTodo}>Add</button>
<button onClick={clearAll}>Clear All</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
Functional State Updates
When the new state depends on the previous state, use the functional form of setState. This ensures you're working with the most current state value.
function Counter() {
const [count, setCount] = useState(0);
const incrementByThree = () => {
// WRONG: Only increments by 1
// setCount(count + 1);
// setCount(count + 1);
// setCount(count + 1);
// CORRECT: Each update uses the latest value
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementByThree}>
Increment by 3
</button>
</div>
);
}
Best Practice: Use the functional form setState(prev => ...) whenever your new state depends on the previous state. This prevents bugs related to stale state values.
Lifting State Up
Sometimes, multiple components need to share the same state. In React, we handle this by moving the state to their closest common parent component and passing it down via props.
function TemperatureCalculator() {
const [temperature, setTemperature] = useState('');
return (
<div>
<h2>Temperature Converter</h2>
<TemperatureInput
scale="c"
temperature={temperature}
onTemperatureChange={setTemperature}
/>
<TemperatureInput
scale="f"
temperature={temperature}
onTemperatureChange={setTemperature}
/>
<BoilingVerdict celsius={parseFloat(temperature)} />
</div>
);
}
function TemperatureInput({ scale, temperature, onTemperatureChange }) {
const scaleNames = { c: 'Celsius', f: 'Fahrenheit' };
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input
value={temperature}
onChange={(e) => onTemperatureChange(e.target.value)}
/>
</fieldset>
);
}
function BoilingVerdict({ celsius }) {
if (isNaN(celsius)) return null;
return (
<p>
{celsius >= 100
? 'The water would boil.'
: 'The water would not boil.'}
</p>
);
}
Lifting State Up: When two or more components need to share state, move the state to their common parent and pass it down as props along with functions to update it. This is called "lifting state up".
Exercise 1: Counter with Multiple Operations
Create a AdvancedCounter component with the following features:
- Display the current count (starting at 0)
- Increment button (+1)
- Decrement button (-1)
- Increment by 5 button (+5)
- Reset to 0 button
- Display the total number of button clicks separately
Hint: You'll need two state variables: one for count and one for totalClicks.
Exercise 2: Shopping Cart
Build a simple shopping cart component with state management:
- Array of products (id, name, price, quantity)
- Display each product with its details
- Increase quantity button for each product
- Decrease quantity button (min 0)
- Remove product button
- Display total price at the bottom
Bonus: Add an input to add new products to the cart.
Exercise 3: Form with Multiple Fields
Create a user registration form with state for multiple fields:
- Username (text input)
- Email (email input)
- Age (number input)
- Subscribe to newsletter (checkbox)
- Display all values in real-time below the form
- Submit button that logs the complete user object
- Clear button that resets all fields
Hint: Use a single state object with multiple properties.