If you know React, you’ve probably heard of "state." State is the "brain" of a component, storing data that can change and determining what the component displays. In the past, you had to use Class Components to work with state. But since React 16.8, a revolution began with the introduction of Hooks, and useState
is the most basic and important Hook, ushering in the era of Functional Components.
This article will help you master useState
, from the basics to advanced techniques. Let’s dive in!
What is the useState Hook? 💡
Imagine a Functional Component as someone with short-term memory. Every time it re-renders, it forgets everything. useState
is a Hook (a special function) that lets you "plug in" memory to that Functional Component. Now, the component can remember, read, and update its state across renders—without writing a class.
Simply put, useState
brings state management to function components, making your code cleaner, more readable, and easier to maintain.
Syntax and How It Works ✨
To use useState
, first import it from React:
import React, { useState } from 'react'
The basic syntax of useState
looks like this:
const [state, setState] = useState(initialState)
Let’s break it down:
initialState
: The initial value for your state. It can be anything: a number, string, boolean, object, or array. React only uses this value on the first render.state
: 📦 The variable holding the current state value. You can name it anything (e.g.,count
,userName
,isActive
).setState
: ⚙️ A special function React provides to update the state. When you call it with a new value, React schedules a re-render with the updated state. By convention, this is namedset
+ StateVariable (e.g.,setCount
,setUserName
).[...]
(Array Destructuring): This JavaScript syntax "unpacks" the two elements returned byuseState
(the state value and the updater function) into separate variables.
Classic Example: Counter
This is the "Hello, World!" of useState
and will help you understand it instantly.
import React, { useState } from 'react'
function Counter() {
// 1. Declare a new state variable called "count"
// Initial value is 0
const [count, setCount] = useState(0)
return (
<div>
<p>You clicked {count} times</p>
{/* 2. On click, call setCount to update state */}
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
export default Counter
How it works:
- First render:
useState(0)
is called.count
is0
. The component displays "You clicked 0 times." - When you click the button: The
onClick
handler runs, callingsetCount(count + 1)
(i.e.,setCount(1)
). - React schedules a re-render: React sees the state has changed and re-renders the
Counter
component. - Second render:
useState
is called again, but React knows this component already has state, so it returns the current value, which is1
. Nowcount
is1
. The component displays "You clicked 1 time."
This process repeats every time you click.
Advanced Concepts and Golden Rules 🏆
Using useState
isn’t just about counting. To become a "pro," you need to master these rules and techniques.
1. Updating State That’s an Object or Array
This is a common beginner mistake. Golden rule: Never mutate state directly! Always create a new object or array.
Why? React compares the old and new state to decide whether to re-render. If you only change a property inside the old object, React sees the same reference and may skip the re-render.
For Objects:
const [user, setUser] = useState({ name: 'Alice', age: 25 })
// ❌ WRONG: Mutating directly
// const handleAgeIncrease = () => {
// user.age = user.age + 1;
// setUser(user); // React may not re-render
// };
// ✅ RIGHT: Use spread syntax (...) to create a new object
const handleAgeIncrease = () => {
setUser({ ...user, age: user.age + 1 })
}
For Arrays:
const [todos, setTodos] = useState(['Learn React', 'Go to sleep'])
// ❌ WRONG: Mutating directly
// const addTodo = () => {
// todos.push('Do homework');
// setTodos(todos); // React may not re-render
// };
// ✅ RIGHT: Use spread syntax (...) to create a new array
const addTodo = (newTodo) => {
setTodos([...todos, newTodo])
}
2. Updating State Based on Previous State
Imagine you have a button that increases a value twice in a row:
const [count, setCount] = useState(0)
const handleIncreaseByTwo = () => {
setCount(count + 1) // count is still 0 here
setCount(count + 1) // count is still 0 here
}
// Result: count only increases by 1, not 2!
This happens because state updates in React are asynchronous. React may batch multiple setCount
calls for performance. Both calls read count
as 0
.
Solution: Pass a function to setState
. This function receives the previous state (prevState
) and returns the new state. This is the safe, recommended way when the new state depends on the old state.
const handleIncreaseByTwoCorrectly = () => {
setCount((prevCount) => prevCount + 1)
setCount((prevCount) => prevCount + 1)
}
// Result: count increases by 2!
3. The Golden Rules of Hooks ⚠️
React enforces 2 rules when using Hooks:
- Only call Hooks at the top level: Don’t call Hooks inside loops, conditions, or nested functions.
- Why: React relies on the order of Hook calls to match state to each Hook.
- Only call Hooks from React Functional Components: Don’t call Hooks from regular JavaScript functions.
Practical Example: Simple Login Form
Let’s apply what you’ve learned to build a simple login form, managing state for both username and password.
import React, { useState } from 'react'
function LoginForm() {
const [formData, setFormData] = useState({
username: '',
password: '',
})
const handleChange = (event) => {
const { name, value } = event.target
// Use functional update and spread syntax for safe updates
setFormData((prevData) => ({
...prevData,
[name]: value, // Use computed property name
}))
}
const handleSubmit = (event) => {
event.preventDefault() // Prevent page reload
alert(
`Logging in with Username: ${formData.username} and Password: ${formData.password}`,
)
}
return (
<form onSubmit={handleSubmit}>
<label>
Username:
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</label>
<br />
<label>
Password:
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</label>
<br />
<button type="submit">Login</button>
</form>
)
}
In this example, we use a single state object to manage all form data—a very common and effective technique.
Conclusion: useState is the Foundation of React Apps
useState
is a simple Hook in terms of syntax but incredibly powerful. It’s the building block for modern React apps. By understanding how it works, the rules for using it, and common patterns, you have a great tool for creating flexible, manageable components.
From here, you’re ready to explore other Hooks like useEffect
for side effects, useContext
for global state, and more.
Congratulations on taking your first step with React Hooks!