React useState Hook Tutorial: Learn State with 4 Easy Examples
Learn the React useState hook step by step. Build a counter, a show/hide toggle, a live input and a to-do list with simple code you can copy.
React useState Hook: A Complete Beginner-Friendly Guide
What is useState in React?
useState is a React Hook that allows a component to store and manage data between renders. When the state changes, React re-renders the component to display the updated information.
Think of a cricket scoreboard. The scoreboard displays the current score, and whenever the score changes, the display updates automatically. You don't need to rebuild the entire scoreboard manually.
React state works similarly. It stores values such as counters, form inputs, menu visibility, and to-do lists.
A normal JavaScript variable does not persist reliably between component renders, and changing it does not tell React to update the UI. The useState Hook solves both problems.
1. useState Syntax
First, import useState from React:
import { useState } from "react";
Then call it inside your component:
const [value, setValue] = useState(initialValue);
The three important parts are:
- value โ the current state value.
- setValue โ the function used to update the state.
- initialValue โ the initial value, used when the component is first initialized.
State can store numbers, strings, booleans, arrays, objects, and other JavaScript values.
Important rules:
- Call Hooks at the top level of a React component, not inside loops, conditions, or nested functions.
- Never modify state directly. Use its setter function.
- State updates cause React to re-render the component when appropriate.
2. Example: Counter Using Number State
Let's create a counter with Increase, Decrease, and Reset buttons.
import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <h2>Count: {count}</h2> <button onClick={() => setCount((c) => c + 1)}> Increase </button> <button onClick={() => setCount((c) => c - 1)}> Decrease </button> <button onClick={() => setCount(0)}> Reset </button> </div> ); }
How does it work?
- useState(0) initializes the counter to 0.
- Clicking Increase requests an update to the previous count plus one.
- Clicking Decrease requests an update to the previous count minus one.
- Clicking Reset sets the count back to 0.
- React re-renders the component and displays the updated count.
Pro tip: When the next state depends on the previous state, use the functional updater form:
setCount((previousCount) => previousCount + 1);
React passes the latest queued state to the updater, making it safer when multiple updates are scheduled together.
3. Example: Show and Hide Content
Boolean state is useful when something can be either true or false, such as a dropdown, modal, menu, or password visibility toggle.
import { useState } from "react"; export default function ShowHide() { const [isOpen, setIsOpen] = useState(false); return ( <div> <button onClick={() => setIsOpen((open) => !open)}> {isOpen ? "Hide Details" : "Show Details"} </button> {isOpen && ( <p>React remembers whether this panel is open.</p> )} </div> ); }
How does it work?
- isOpen starts as false, so the paragraph is hidden.
- Clicking the button toggles the state.
- The ternary operator (? :) changes the button label.
- The logical AND operator (&&) displays the paragraph only when isOpen is true.
Naming tip: Use descriptive boolean names such as isOpen, isLoading, and hasError.
4. Example: Live Text Input
React can control an input field using state. This is called a controlled component.
import { useState } from "react"; export default function Greeting() { const [name, setName] = useState(""); return ( <div> <input type="text" value={name} onChange={(event) => setName(event.target.value)} placeholder="Enter your name" /> <p> {name ? `Hello, ${name}!` : "Please enter your name."} </p> <p>Characters: {name.length}</p> </div> ); }
How does it work?
- The initial value is an empty string.
- Every keystroke triggers onChange.
- event.target.value contains the current input text.
- setName() updates the state.
- React re-renders the component, updating the greeting and character count.
Notice that name.length is calculated directly from the existing state. You don't need a separate state variable for the character count.
Rule of thumb: If a value can be calculated from existing props or state, calculate it instead of storing duplicate data.
5. Example: To-Do List Using Array State
Arrays let you manage collections of data, such as tasks, products, comments, or notifications.
The key rule is to create a new array instead of modifying the existing state array.
import { useState } from "react"; export default function TodoList() { const [text, setText] = useState(""); const [todos, setTodos] = useState([]); function addTodo(event) { event.preventDefault(); if (!text.trim()) return; setTodos((previousTodos) => [ ...previousTodos, { id: crypto.randomUUID(), title: text.trim(), done: false, }, ]); setText(""); } function toggleTodo(id) { setTodos((previousTodos) => previousTodos.map((todo) => todo.id === id ? { ...todo, done: !todo.done } : todo ) ); } function deleteTodo(id) { setTodos((previousTodos) => previousTodos.filter((todo) => todo.id !== id) ); } const remainingTasks = todos.filter((todo) => !todo.done).length; return ( <div> <form onSubmit={addTodo}> <input value={text} onChange={(event) => setText(event.target.value)} placeholder="Enter a task" /> <button type="submit">Add Task</button> </form> <p>Tasks remaining: {remainingTasks}</p> <ul> {todos.map((todo) => ( <li key={todo.id}> <label> <input type="checkbox" checked={todo.done} onChange={() => toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.done ? "line-through" : "none", }} > {todo.title} </span> </label> <button type="button" onClick={() => deleteTodo(todo.id)}> Delete </button> </li> ))} </ul> </div> ); }
How does it work?
This component uses two state variables:
- text stores the current input value.
- todos stores the list of tasks.
Each operation creates a new array:
ActionMethodPurposeAdd a task[...previousTodos, newTodo]Creates a new array with the new taskToggle a taskmap()Creates a new array and updates the matching taskDelete a taskfilter()Creates a new array without the selected task
The key prop helps React identify each task when rendering a list. The remaining task count is calculated from todos, so it doesn't need its own state variable.
Avoid using methods such as todos.push(newTodo) to update state. They mutate the existing array rather than creating a new state value.
6. Common useState Mistakes
Mistake 1: Modifying state directly
count++; todos.push(newTodo);
Why it's wrong: These operations mutate existing values and don't correctly notify React of the intended state change.
Better approach:
setCount((count) => count + 1); setTodos((todos) => [...todos, newTodo]);
Mistake 2: Expecting state to change immediately
setCount(5); console.log(count);
The logged value may still be the value from the current render. State setters schedule an update; they don't change the existing render's state variable.
Mistake 3: Calling a setter during render
<button onClick={setCount(1)}>Set Count</button>
This calls the setter while rendering instead of waiting for a click.
Correct approach:
<button onClick={() => setCount(1)}> Set Count </button>
Mistake 4: Storing calculated values unnecessarily
If you already have todos, calculate the remaining tasks:
const remainingTasks = todos.filter((todo) => !todo.done).length;
You don't need another state variable for a value that can be calculated from existing state.
7. Summary
- useState lets a component remember data between renders.
- It returns the current state and a setter function.
- Updating state tells React to render the component again when appropriate.
- Use functional updates when the next value depends on the previous value.
- Use immutable updates for arrays and objects.
- Calculate derived values instead of storing unnecessary duplicate state.
Practice Challenge
Extend the to-do list with a Clear Completed button.
Requirements:
- Allow users to mark tasks as completed.
- Remove all completed tasks when the button is clicked.
- Keep the remaining task count accurate.
- Show a message when there are no tasks left.
Try implementing it yourself before checking a solution.
Frequently Asked Questions
What is the difference between props and state?
Props are values passed to a component by its parent. State is data managed by the component using a Hook such as useState. Props are read-only from the receiving component's perspective, while state can be updated through its setter.
Can I use useState more than once?
Yes. A component can call useState multiple times to manage separate pieces of state, such as an input value and a list of tasks.
Why doesn't state update immediately?
React schedules state updates and renders the component with the new state. The variable in the current render continues to hold its existing value.
What should I learn after useState?
Next, learn the useEffect Hook, which lets you synchronize a component with external systems, such as fetching data, setting up subscriptions, or managing timers.
Continue learning with the next SamWithCode tutorial: React useEffect Hook.
Embedded resource: youtube