Introduction to useState
The useState hook is one of React's most fundamental hooks. It allows functional components to have state variables, which was previously only possible with class components. State in React represents data that changes over time and affects what is rendered on the screen.
Key Points:
- The
useStatehook returns an array with two elements: the current state value and a function to update it - State updates trigger component re-renders
- Each state variable is independent and can be updated separately
- When updating state based on previous state, always use the callback form
Files We Will Work With
src/components/UseState/UseState.jsx- Main component we'll be implementingsrc/components/UseState/UseState.css- Styles for our componentsrc/main.jsx- Entry point that renders our componentindex.html- Base HTML file
George Polya's 4-Step Problem Solving Method
Step 1: Understand the Problem
In this practice, we need to:
- Create a component that uses the
useStatehook for two different purposes - Implement a theme switcher (light/dark) using state
- Implement a counter with increment and decrement functionality
- Ensure our state updates correctly based on previous state
Step 2: Devise a Plan
- Import the
useStatehook from React - Create a state variable for the theme with an initial value of 'light'
- Apply the theme class to our component's outer div
- Add buttons to switch between light and dark themes
- Create a state variable for the counter with an initial value of 0
- Display the current count value
- Add buttons to increment and decrement the counter
- Update the increment and decrement functions to use callback form
- (Bonus) Create a toggle theme button
Step 3: Execute the Plan
Let's implement our solution step by step:
Step 1-3: Creating and applying theme state
// Import useState hook and CSS
import { useState } from 'react';
import './UseState.css';
function UseState() {
// Create theme state with initial value of 'light'
const [theme, setTheme] = useState('light');
return (
// Apply the theme class to the outer div
<div className={`state ${theme}`}>
<h1>UseState Component</h1>
{/* We'll add buttons here */}
<div className="button-container">
{/* Counter display will go here */}
</div>
</div>
);
}
export default UseState;
Step 4: Adding theme switch buttons
<h1>UseState Component</h1>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('light')}>Light</button>
<div className="button-container">
Step 5-6: Creating and displaying counter state
function UseState() {
const [theme, setTheme] = useState('light');
// Add counter state with initial value of 0
const [count, setCount] = useState(0);
return (
<div className={`state ${theme}`}>
<h1>UseState Component</h1>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('light')}>Light</button>
<div className="button-container">
{/* Display the current count */}
<h2>{count}</h2>
{/* We'll add increment/decrement buttons here */}
</div>
</div>
);
}
Step 7: Adding counter buttons
<div className="button-container">
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
Step 8: Updating to use callback form
<div className="button-container">
<h2>{count}</h2>
{/* Use callback form for state updates based on previous state */}
<button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>Decrement</button>
</div>
Bonus: Toggle theme button
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
Step 4: Review the Solution
Let's review our complete solution:
Final UseState.jsx
import { useState } from 'react';
import './UseState.css';
function UseState() {
// Theme state - controls the background color
const [theme, setTheme] = useState('light');
// Counter state - controls the displayed number
const [count, setCount] = useState(0);
return (
<div className={`state ${theme}`}>
<h1>UseState Component</h1>
{/* Theme buttons */}
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('light')}>Light</button>
{/* Toggle theme button */}
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<div className="button-container">
{/* Counter display and buttons */}
<h2>{count}</h2>
<button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>Decrement</button>
</div>
</div>
);
}
export default UseState;
Our solution successfully implements:
- A theme state that toggles between light and dark
- A counter state that can be incremented and decremented
- Proper use of callback functions when updating state based on previous values
- A bonus toggle theme button that switches between themes
Detailed Explanation
What is useState?
The useState hook is a function that lets you add state to functional components. When called, it returns a pair: the current state value and a function that lets you update it.
const [stateVariable, setStateVariable] = useState(initialValue);
Each call to useState creates a separate piece of state. You can call it multiple times to have multiple state variables.
Theme State Explained
Our theme state is a simple string that can be either 'light' or 'dark'. We use this state to apply different CSS classes to our component:
const [theme, setTheme] = useState('light');
// ...
<div className={`state ${theme}`}>
When the theme changes, React re-renders the component and applies the new CSS class, changing the background color.
Counter State Explained
Our counter state is a number that starts at 0. We display it directly in our JSX and provide buttons to change it:
const [count, setCount] = useState(0);
// ...
<h2>{count}</h2>
<button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
Every time a button is clicked, the state updates and the component re-renders with the new count value.
Why use the callback form?
When updating state based on the previous state value, you should always use the callback form:
// Not ideal when updating based on previous state
setCount(count + 1);
// Better approach
setCount(prevCount => prevCount + 1);
This is because React state updates can be batched together for performance reasons. If multiple state updates happen in quick succession, using the variable directly might lead to stale values. The callback form guarantees that you're working with the most current state value.
Think of it like this: If you tell three people to add 1 to a number on a whiteboard, they might all read "5" and write "6", resulting in "6" instead of "8". But if you tell them to "add 1 to whatever number you see", you'll get the correct final value.
Real-World Applications
Theme Switching
Many modern websites and applications offer light and dark modes to improve user experience. Our theme state implementation is a simplified version of this common feature.
In a real application, you might store the user's theme preference in local storage or a database to persist it between sessions.
Form Inputs
The useState hook is commonly used to manage form inputs in React. Each input field typically has its own state variable that updates as the user types.
const [username, setUsername] = useState('');
// ...
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
Shopping Cart
Our counter implementation is similar to quantity controls in a shopping cart. In a real application, you might use useState to track quantities of different items.
const [cartItems, setCartItems] = useState([]);
const incrementQuantity = (itemId) => {
setCartItems(prevItems =>
prevItems.map(item =>
item.id === itemId
? {...item, quantity: item.quantity + 1}
: item
)
);
};
Common Pitfalls and Tips
Forgetting Dependencies
When using useState with other hooks like useEffect, make sure to properly list all dependencies to avoid stale state issues.
Direct State Mutation
Never modify state directly. Always use the setter function provided by useState.
// WRONG - direct mutation
count = count + 1;
// CORRECT - using setter function
setCount(count + 1);
Complex State
For complex state (objects, arrays), remember that useState doesn't merge objects like class component's setState does. You need to spread the previous state manually.
const [user, setUser] = useState({ name: '', email: '' });
// Update only the email
setUser(prevUser => ({ ...prevUser, email: 'new@example.com' }));
Further Learning
Additional Exercises
- Create a form with multiple input fields managed by
useState - Implement a to-do list where items can be added, removed, and marked as complete
- Create a quiz app that keeps track of the current question and user's score
Related Hooks
After mastering useState, explore these related React hooks:
useReducer- For more complex state logicuseEffect- For side effects in functional componentsuseContext- For sharing state across components without prop drilling