Skip to content

Join the Seedly owners community →

React

State & useState

Learn how React components remember things using state, and how the useState hook lets you add interactivity to your apps

Written by 12 min read1 activity
Pixl, your presenter

Pixl presents

A component that forgets everything on every click is useless. useState gives it a memory. You're welcome.A component that forgets everything on every click is useless. useState gives it a memory. You're welcome.

Pixl slides an amber bead across a wooden abacus, earlier beads staying in place
State gives a component a memory that survives each update

So far your components only show static stuff. Real apps have to respond to people, though. A counter that goes up when you click it. A form that keeps track of what you type. A toggle that flips between light and dark mode. Every one of those needs state.

What is State?#

State is data that can change over time, and when it changes, your component re-renders. Think of it as your component's memory... it remembers values from one render to the next.

Here's the big difference between a regular variable and state.

// This does NOT work - the UI won't update
function BrokenCounter() {
  let count = 0;
 
  return (
    <button onClick={() => { count += 1; }}>
      Clicked {count} times
    </button>
  );
}

Click that button and count really does change in memory. React just has NO idea it happened. So the screen sits there at "Clicked 0 times" forever (super frustrating the first time you hit it). Regular variables don't trigger re-renders.

Enter useState#

The useState hook is how React gives a component memory. It hands you back two things, the current value and a function to update it.

import { useState } from 'react';
 
function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

Now when you click the button, setCount basically tells React "hey, the count changed!" React re-renders the component with the new value and the screen updates. Donezo.

Breaking Down useState#

Step 1. Import the Hook#

Before you can use useState, you have to import it from React.

import { useState } from 'react';

Step 2. Call useState with an Initial Value#

Pass in the starting value as an argument. This value only matters on the very first render.

const [count, setCount] = useState(0);       // starts at 0
const [name, setName] = useState('');         // starts as empty string
const [items, setItems] = useState([]);       // starts as empty array
const [user, setUser] = useState(null);       // starts as null

Step 3. Use Destructuring to Get the Value and Setter#

useState returns an array with exactly two items in it. You can name them whatever you want, but the convention everybody follows is [thing, setThing].

const [isVisible, setIsVisible] = useState(true);
const [score, setScore] = useState(100);
const [email, setEmail] = useState('');

Updating State#

To change state, you call the setter function and hand it the new value.

function ToggleButton() {
  const [isOn, setIsOn] = useState(false);
 
  return (
    <button onClick={() => setIsOn(!isOn)}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

When you call setIsOn(!isOn), React schedules a re-render. Your component function runs again, useState hands back the new value, and the UI updates to match.

Updating State Based on Previous Value#

Pixl offers a new copied card with one added line while holding the untouched original away
Update state by making a new copy, never by editing the old one

When your new state depends on the old state, use the functional form of the setter.

// Less reliable (might use stale value):
setCount(count + 1);
 
// More reliable (always uses the latest value):
setCount(prev => prev + 1);

The functional form gets handed the most current value as an argument. That matters when a bunch of updates happen quickly or inside event handlers, where the plain version can grab a stale value without you noticing.

Multiple State Variables#

A component can have as many useState calls as it needs. There's no limit.

function SignupForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  const [agreedToTerms, setAgreedToTerms] = useState(false);
 
  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <input
        type="number"
        value={age}
        onChange={e => setAge(Number(e.target.value))}
      />
      <label>
        <input
          type="checkbox"
          checked={agreedToTerms}
          onChange={e => setAgreedToTerms(e.target.checked)}
        />
        I agree to the terms
      </label>
    </form>
  );
}

State with Objects and Arrays#

You can store objects and arrays in state too. Just remember you have to create a new object or array whenever you update it, instead of changing (mutating) the one you already have.

function TodoList() {
  const [todos, setTodos] = useState([]);
 
  function addTodo(text) {
    // Create a new array with the existing items plus the new one
    setTodos([...todos, { id: Date.now(), text }]);
  }
 
  function removeTodo(id) {
    // Create a new array without the removed item
    setTodos(todos.filter(todo => todo.id !== id));
  }
 
  return (
    <div>
      <button onClick={() => addTodo('New task')}>Add</button>
      {todos.map(todo => (
        <p key={todo.id}>
          {todo.text}
          <button onClick={() => removeTodo(todo.id)}>Delete</button>
        </p>
      ))}
    </div>
  );
}

TL;DR#

  • State is data that changes over time and triggers a re-render when it's updated
  • useState returns an array, [currentValue, setterFunction]
  • Always update state with the setter function (never change it directly)
  • Use the functional form (prev => prev + 1) when the new value depends on the old one
  • One component can have several useState calls for different pieces of data
  • When you update objects or arrays, make a new copy instead of mutating the old one

What's Next?#

Your components can remember and update data now. Next you'll learn how to show or hide things based on that data. That's conditional rendering, which is just a fancy way of saying "show different UI depending on what's in state"...

This lesson ends with a short activity.