Skip to main content

What is state?

State is data that can change over time - a form input, a toggle button, items in a shopping cart. When state changes, React automatically updates the UI to match. The useState hook is how you add state to function components. You’ll use it constantly.

Basic useState example

Counter.jsx
useState(0) returns an array with two items:
  1. The current value (count)
  2. A function to update it (setCount)
We use array destructuring to grab both: const [count, setCount] = useState(0).
Always name the setter function set + the state variable name: count/setCount, user/setUser, isOpen/setIsOpen. This is a strong convention in React.

How state updates work

State updates are scheduled (and often batched). When you call setCount(5), React schedules a re-render but doesn’t update count immediately inside the current event handler. The new value will be available on the next render.
Don’t try to use the updated state value immediately after calling the setter in the same event handler. It won’t be updated yet. React batches state updates for performance.

Updating state based on previous value

When updating based on the previous state, use the updater function form: setCount(prev => prev + 1). This ensures you always get the latest value, even if multiple updates happen.
If your new state depends on the old state, use the updater function: setState(prev => ...). If it doesn’t, you can pass the value directly: setState(newValue).

State with objects

UserProfile.jsx
Never mutate state directly. Use the spread operator ... to create a new object with the updated property. React only detects changes when you pass a new object to the setter.
Direct mutation (user.age = 26) won’t trigger a re-render. React compares the old and new values - if they’re the same object reference, it thinks nothing changed.

Multiple state variables

UserForm.jsx
You can have as many useState calls as you need. Each one is independent. Here we track form inputs, loading state, and errors separately.
The disabled={isSubmitting} prevents users from submitting the form multiple times while a request is in flight. This is a crucial UX pattern.

State with arrays

TodoList.jsx
For arrays, use array methods that return new arrays:
  • Add item: [...array, newItem]
  • Update item: array.map(item => item.id === id ? updatedItem : item)
  • Remove item: array.filter(item => item.id !== id)
Never use .push(), .pop(), .splice() or other mutating methods on state arrays. Always create a new array with spread ... or array methods like .map() and .filter().

When to split state vs keep together

Group related data in objects, but keep unrelated concerns (like loading states and errors) separate.

Common mistakes

React only detects changes when you call the setter with a NEW object or array. Mutating the existing state and passing it back won’t trigger a re-render because it’s still the same reference.
When multiple state updates happen in the same function, use the updater function form to ensure you’re always working with the latest value.
State updates are asynchronous. The new value won’t be available until the component re-renders. Don’t try to use the updated state immediately after calling the setter.
When rendering lists, each item needs a unique key prop. This helps React efficiently update the DOM when items are added, removed, or reordered. Never use array index as a key if the list can change.

What’s next?

State lets you manage data within a component. Now let’s learn how to fetch data from your backend when a component first loads.

Side effects with useEffect

Fetch data when your component mounts