Skip to content

Expert · React concepts · topic 58 of 64 · 40 min

State and events

Explanation

State is data a component remembers and can change, such as the items in a cart or whether a menu is open. useState gives you the current value and a setter; calling the setter makes React render again. State is a snapshot, so use an updater function when the next value depends on the last one, and never change arrays or objects in place: make new copies.

What is state?

State is data that a component remembers between renders and that can change over time: the items in a cart, the text typed in a box, whether a menu is open. A normal variable is not enough. It starts again from its first value on every render, and changing it does not tell React to draw the screen again.

javascript
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}
useState(0) returns the current value and a function to set a new one (React code, runs in a React project)
Careful: useState is a hook: a special React function whose name starts with "use". Call hooks only at the top level of a component or a custom hook. Never call them inside an if, a loop or a nested function, because React matches each hook by its order.

Handling events

You attach an event handler with a camelCase prop such as onClick, onChange or onSubmit. Pass the function itself: onClick={handleClick}. Do not call it: onClick={handleClick()} would run during rendering, not on the click. To pass an argument, wrap the call in an arrow function.

javascript
function LikeButton() {
  const [liked, setLiked] = useState(false);

  function handleClick() {
    setLiked(!liked);
  }

  return (
    <button onClick={handleClick} aria-pressed={liked}>
      {liked ? 'Liked' : 'Like'}
    </button>
  );
}

// With an argument:
<button onClick={() => removeItem(item.id)}>Remove</button>
onClick={handleClick} passes the function; React calls it on each click (React code, runs in a React project)

State is a snapshot

Think of it like this: State is like a photo of the scoreboard taken at the start of an over. During that over, everyone reads the same photo, even after a boundary is hit. The new score appears only in the next photo, which is the next render.
javascript
// count is 0
function handleClick() {
  setCount(count + 1); // asks for 0 + 1
  setCount(count + 1); // still asks for 0 + 1
  setCount(count + 1); // still 0 + 1  -> after the render, count is 1
}

function handleClickFixed() {
  setCount((c) => c + 1); // 0 -> 1
  setCount((c) => c + 1); // 1 -> 2
  setCount((c) => c + 1); // 2 -> 3  -> after the render, count is 3
}
The setter does not change count now. Pass an updater function when the next value depends on the previous one (React code, runs in a React project)

Never mutate state: make new copies

React checks whether state changed by comparing the old and new value with Object.is. If you push into the same array and set it again, React sees the same array and may skip the update. So always build a new array or object. Spread (...), map and filter all return new ones.

TaskWrong (changes the old value)Right (makes a new value)
Add an itemitems.push(item)setItems([...items, item])
Remove an itemitems.splice(index, 1)setItems(items.filter((i) => i.id !== id))
Change one itemitems[0].done = truesetItems(items.map((i) => (i.id === id ? { ...i, done: !i.done } : i)))
Change an object fielduser.name = 'Asha'setUser({ ...user, name: 'Asha' })
Change a nested fielduser.address.pin = '560001'setUser({ ...user, address: { ...user.address, pin: '560001' } })

Lifting state up

Sometimes two components need the same data. For example, the product list adds to the cart and the header shows the cart count. Move the state up to their closest common parent, then pass the value and the functions that change it down as props. This is called lifting state up.

javascript
function Shop() {
  const [cart, setCart] = useState([]);

  function handleAdd(product) {
    setCart((current) => [...current, product]);
  }

  return (
    <>
      <Header cartCount={cart.length} />
      <ProductList onAdd={handleAdd} />
    </>
  );
}
Shop owns the cart; Header reads it and ProductList asks to change it (React code, runs in a React project)

useReducer for bigger state logic

When many different actions change the same state (add, remove, change quantity, clear), the update code spreads across many handlers. useReducer collects it in one pure function called a reducer. It takes the current state and an action object and returns the next state. Handlers just dispatch (send) actions.

javascript
import { useReducer } from 'react';

function cartReducer(cart, action) {
  switch (action.type) {
    case 'added':
      return [...cart, { ...action.item, qty: 1 }];
    case 'removed':
      return cart.filter((line) => line.id !== action.id);
    default:
      return cart;
  }
}

function Cart() {
  const [cart, dispatch] = useReducer(cartReducer, []);
  // in a handler:
  // dispatch({ type: 'added', item: rice });
  // dispatch({ type: 'removed', id: 'p1' });
}
The reducer holds all the update rules; components only dispatch actions (React code, runs in a React project)
Tip: A reducer is plain JavaScript with no React inside, so you can test it without a browser. That is exactly what the tasks below do. Reducers must be pure: no fetch, no timers, no changing the old state.

Watch the lesson

State and events · 1/7

state changes -> React re-renders

State is the memory of a component. When state changes, React draws the component again.

Prefer a video by a teacher? Find free videos on “react useState useReducer tutorial state snapshot immutable updates” on YouTube ↗

In real life: A kirana billing app

A shop owner's billing app keeps the bill in state managed by a reducer with actions like added, qtyChanged and cleared. The bill state lives in the BillingScreen component (lifted up), so ProductSearch can add items and BillTotal can show the sum in rupees. Each change creates a new array, so React always notices it and redraws the total.

Practice (0/3 done)

Task 1 · Code · Javascript

React keeps a queue of the updates you asked for and applies them in order. Each update is either a plain value (the new state) or an updater function (it gets the previous state and returns the next). Write applyUpdates(initial, updates) that starts from initial, applies every update in order, and returns the final state. Examples: applyUpdates(0, [1, 1, 1]) returns 1 (like calling setCount(count + 1) three times when count is 0). applyUpdates(0, [c => c + 1, c => c + 1, c => c + 1]) returns 3.

Task 2 · Code · Javascript

Write a reducer cartReducer(state, action) for a shopping cart. state is an array of lines { id, name, price, qty }. Handle these actions and NEVER change the old state or old line objects: (1) { type: "added", item } - if a line with item.id exists, return a new array where that line has qty + 1; otherwise add { ...item, qty: 1 } at the end. (2) { type: "removed", id } - remove that line. (3) { type: "qtyChanged", id, qty } - set that line's qty; if qty is 0 or less, remove the line. (4) { type: "cleared" } - return an empty array. For any other type, return the same state.

Task 3 · Practice

In your Vite project, build a small shop with lifted state. A Shop component owns the cart with useReducer and your cartReducer. A ProductList shows 4 products with an "Add" button each, a Header shows the cart count, and a CartPanel lists the lines with + and - buttons that dispatch qtyChanged. Open React DevTools and watch the state change as you click.

Quick quiz

Question 1

1.Why can't you keep a click counter in a normal let variable inside a component?

Question 2

2.What is wrong with <button onClick={save()}>Save</button>?

Question 3

3.count is 0. A click handler calls setCount(count + 1) three times. What is count after the next render?

Question 4

4.Which line correctly adds newItem to an items array kept in state?

Question 5

5.A ProductList and a CartBadge (siblings) both need the cart. Where should the cart state live?

Read more (free)

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

← Previous: Props and composition