useState is a React hook that adds local state to functional components. It returns the current state value and a function to update it.
Syntax: const [state, setState] = useState(initialValue);
Key features:
Usage example:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increase counter
</button>
</div>
);
}
The useState hook is one of React’s core tools, introduced in version 16.8. It allows adding and managing state in functional components, which was previously only possible in class components using this.state
. 🚀
useState returns an array of two elements:
import React, { useState } from 'react';
function Example() {
// Destructuring the array returned by useState
const [count, setCount] = useState(0);
console.log('Current state:', count);
return (
<div>
<p>Value: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
}
When the state update function is called (setCount
in the example above), React:
If the new state depends on the previous one, it’s better to use the functional form:
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
// ❌ Can be problematic with rapid clicks
// setCount(count + 1);
// ✅ Guarantees using the current state
setCount(prevCount => prevCount + 1);
};
return (
<button onClick={handleIncrement}>
Increase: {count}
</button>
);
}
If the initial state requires calculations, you can pass a function:
function ExpensiveInitialState() {
// Function is called only on the first render
const [state, setState] = useState(() => {
console.log('Calculating initial state...');
return calculateExpensiveValue();
});
return (
<div>
<p>Value: {state}</p>
<button onClick={() => setState(state + 1)}>
Update
</button>
</div>
);
}
React uses Object.is
to compare values:
function ObjectStateExample() {
const [person, setPerson] = useState({ name: 'John', age: 30 });
const updateAge = () => {
// ❌ Won't trigger re-render if age is the same
// person.age = 31;
// setPerson(person);
// ✅ Creates a new object, triggers re-render
setPerson({ ...person, age: 31 });
};
return (
<div>
<p>Name: {person.name}, Age: {person.age}</p>
<button onClick={updateAge}>
Update age
</button>
</div>
);
}
function FormExample() {
const [name, setName] = useState('');
const [age, setAge] = useState(0);
const [isActive, setIsActive] = useState(false);
return (
<form>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Enter name"
/>
<input
type="number"
value={age}
onChange={e => setAge(Number(e.target.value))}
placeholder="Enter age"
/>
<label>
<input
type="checkbox"
checked={isActive}
onChange={e => setIsActive(e.target.checked)}
/>
Active
</label>
</form>
);
}
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
// Create a new array with the new item
setTodos([...todos, input]);
setInput('');
}
};
const removeTodo = (index) => {
// Filter the array, excluding the item by index
setTodos(todos.filter((_, i) => i !== index));
};
return (
<div>
<input
value={input}
onChange={e => setInput(e.target.value)}
/>
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => removeTodo(index)}>Remove</button>
</li>
))}
</ul>
</div>
);
}
function UserProfile() {
const [profile, setProfile] = useState({
firstName: '',
lastName: '',
email: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
// Update only the changed field, preserving the rest
setProfile(prevProfile => ({
...prevProfile,
[name]: value
}));
};
return (
<form>
<input
name="firstName"
value={profile.firstName}
onChange={handleChange}
placeholder="First Name"
/>
<input
name="lastName"
value={profile.lastName}
onChange={handleChange}
placeholder="Last Name"
/>
<input
name="email"
value={profile.email}
onChange={handleChange}
placeholder="Email"
/>
<div>
<strong>Profile:</strong>
{JSON.stringify(profile, null, 2)}
</div>
</form>
);
}
// Class component
class ClassCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return (
<div>
<p>Counter: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increase
</button>
</div>
);
}
}
// Functional component with useState
function HookCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Counter: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
}
// ❌ Many separate states
function UserFormBad() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
// Many handlers
}
// ✅ Grouping related states
function UserFormGood() {
const [user, setUser] = useState({
firstName: '',
lastName: '',
email: '',
phone: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setUser(prevUser => ({
...prevUser,
[name]: value
}));
};
// One handler for all fields
}
function ExpensiveCalculation({ data }) {
const [count, setCount] = useState(0);
// Heavy calculation is called on every render
const expensiveResult = calculateExpensive(data);
return (
<div>
<p>Result: {expensiveResult}</p>
<button onClick={() => setCount(count + 1)}>
Counter: {count}
</button>
</div>
);
}
// Solution: use useMemo with useState
function OptimizedCalculation({ data }) {
const [count, setCount] = useState(0);
// Calculation is performed only when data changes
const expensiveResult = useMemo(() => {
return calculateExpensive(data);
}, [data]);
return (
<div>
<p>Result: {expensiveResult}</p>
<button onClick={() => setCount(count + 1)}>
Counter: {count}
</button>
</div>
);
}
// ❌ Incorrect: direct mutation
function WrongMutation() {
const [user, setUser] = useState({ name: 'John', age: 30 });
const handleClick = () => {
// This will NOT trigger a re-render!
user.age = 31;
setUser(user);
};
return (
<div>
<p>Name: {user.name}, Age: {user.age}</p>
<button onClick={handleClick}>Update age</button>
</div>
);
}
// ✅ Correct: creating a new object
function CorrectUpdate() {
const [user, setUser] = useState({ name: 'John', age: 30 });
const handleClick = () => {
// Create a new object
setUser({ ...user, age: 31 });
};
return (
<div>
<p>Name: {user.name}, Age: {user.age}</p>
<button onClick={handleClick}>Update age</button>
</div>
);
}
// ❌ Incorrect: relying on immediate update
function AsyncProblem() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // Still the old value here!
};
return (
<button onClick={handleClick}>
Increase: {count}
</button>
);
}
// ✅ Correct: use useEffect to react to changes
function CorrectAsync() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Counter updated:", count);
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
Increase: {count}
</button>
);
}
// ❌ Incorrect: may lead to skipped updates
function MultipleUpdatesWrong() {
const [count, setCount] = useState(0);
const handleMultipleClicks = () => {
// All these calls use the same count value
setCount(count + 1); // count = 0 -> 1
setCount(count + 1); // count = 0 -> 1
setCount(count + 1); // count = 0 -> 1
// Result: count = 1, not 3!
};
return (
<button onClick={handleMultipleClicks}>
Multiple updates: {count}
</button>
);
}
// ✅ Correct: use functional form
function MultipleUpdatesCorrect() {
const [count, setCount] = useState(0);
const handleMultipleClicks = () => {
// Each call gets the current previous value
setCount(prev => prev + 1); // 0 -> 1
setCount(prev => prev + 1); // 1 -> 2
setCount(prev => prev + 1); // 2 -> 3
// Result: count = 3
};
return (
<button onClick={handleMultipleClicks}>
Multiple updates: {count}
</button>
);
}
✅ useState is React’s main hook for:
this.state
and this.setState
from class components✅ Key features:
const [state, setState] = useState(initialValue)
setState(prev => newValue)
useState(() => computeValue())
✅ Best practices:
useState is a fundamental React hook that makes functional components more powerful and flexible, allowing them to manage internal state as effectively as class components. 🚀
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪