🎓 What You Will Learn
- useState Basics: Add state to functional components
- State Updates: How to change state safely
- Multiple States: Manage different pieces of data
- Form Handling: Handle input and form state
- Array State: Add, remove, and update array items
- Object State: Update nested object properties
- Conditional Rendering: Show/hide based on state
- Best Practices: How to use useState correctly
What is useState?
useState is a React Hook that lets you add state to functional components. Before hooks, you had to use class components to have state. Now, any functional component can have state using useState.
State is data that can change over time. When state changes, React automatically re-renders the component to show the new data.
const [value, setValue] = useState(initialValue)Returns: An array with the current value and a function to update it
Basic Syntax
import { useState } from 'react';
export function Counter() {
// Declare state variable
const [count, setCount] = useState(0);
// ^ ^ ^
// | | initial value
// | function to update
// current value
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}Current Count:
0
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
<button onClick={() => setCount(0)}>
Reset
</button>
</div>
);
}How State Updates Work
When you call the setter function (setCount), React:
- 1. Updates the state value
- 2. Re-renders the component with the new value
- 3. Browser updates the display
count = count + 1 won't work. Use setCount(count + 1) instead.Start typing...
import { useState } from 'react';
export function NameInput() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
{name && <p>Hello, {name}!</p>}
</div>
);
}Form Inputs & State
To capture form input, store it in state and update it with onChange event.
export function LoginForm() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
console.log("Username:", username);
console.log("Password:", password);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
);
}import { useState } from 'react';
export function ToggleMessage() {
const [isVisible, setIsVisible] = useState(false);
return (
<>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? "Hide" : "Show"}
</button>
{isVisible && <p>Message is visible!</p>}
</>
);
}Multiple State Variables
You can use useState multiple times in the same component. Each state variable is independent.
export function PersonForm() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [age, setAge] = useState("");
return (
<>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
/>
</>
);
}
import { useState } from 'react';
export function Form() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [age, setAge] = useState("");
return (
<>
<input
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
placeholder="First name"
/>
<input
value={lastName}
onChange={(e) => setLastName(e.target.value)}
placeholder="Last name"
/>
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="Age"
/>
<p>{firstName} {lastName}, {age}</p>
</>
);
}Managing Array State
When state is an array, use the spread operator to create a new array instead of mutating.
const [items, setItems] = useState(["Apple", "Banana"]);
// Add item
setItems([...items, "Orange"]);
// Remove item
setItems(items.filter(item => item !== "Apple"));
// Update item
setItems(items.map(item =>
item === "Banana" ? "Grape" : item
));import { useState } from 'react';
export function ItemList() {
const [items, setItems] = useState(["Apple", "Banana"]);
const [input, setInput] = useState("");
const addItem = () => {
setItems([...items, input]);
setInput("");
};
const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index));
};
return (
<>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button onClick={addItem}>Add</button>
{items.map((item, i) => (
<div key={i}>
{item}
<button onClick={() => removeItem(i)}>Remove</button>
</div>
))}
</>
);
}Managing Object State
When state is an object, use the spread operator to create a new object with updated properties.
const [user, setUser] = useState({
name: "Alice",
email: "[email protected]",
age: 25
});
// Update one property
setUser({ ...user, name: "Bob" });
// Update multiple properties
setUser({
...user,
name: "Charlie",
age: 30
});import { useState } from 'react';
export function UserForm() {
const [user, setUser] = useState({
name: "Alice",
email: "[email protected]",
age: 25
});
const updateField = (field, value) => {
setUser({ ...user, [field]: value });
};
return (
<>
<input
value={user.name}
onChange={(e) => updateField("name", e.target.value)}
/>
<input
value={user.email}
onChange={(e) => updateField("email", e.target.value)}
/>
<input
type="number"
value={user.age}
onChange={(e) => updateField("age", +e.target.value)}
/>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Age: {user.age}</p>
</>
);
}Functional Updates
Sometimes you need to calculate the new state based on the previous state. Use a function as the argument to the setter.
const [count, setCount] = useState(0);
// ✅ GOOD: Use function for updates based on previous state
const increment = () => {
setCount(prevCount => prevCount + 1);
};
// ❌ BAD: This might not work correctly
const badIncrement = () => {
setCount(count + 1); // count might be stale
};- • Updating based on previous state
- • Multiple state updates in quick succession
- • Using state in event handlers
Best Practices
setUserName not setXCommon Mistakes
// ❌ BAD: Mutating state directly
const [user, setUser] = useState({ name: "Alice" });
user.name = "Bob"; // Don't do this!
setUser(user);
// ✅ GOOD: Create new object with spread
setUser({ ...user, name: "Bob" });// ❌ BAD: Direct assignment
let count = 0;
count = count + 1; // This won't trigger re-render!
// ✅ GOOD: Use setState hook
const [count, setCount] = useState(0);
setCount(count + 1); // This triggers re-render// ❌ BAD: Array.push() mutates
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // Don't do this!
setItems(items);
// ✅ GOOD: Create new array with spread
setItems([...items, 4]);What's Next?
- useEffect: Handle side effects and API calls
- useReducer: Manage complex state with actions
- useContext: Avoid prop drilling with global state
- Custom Hooks: Create reusable state logic
- Form Libraries: React Hook Form, Formik for complex forms
Practice with the examples above! 💪 Try modifying the code and running the examples. Build simple apps like counters, todo lists, and forms to master useState!