React · State & Events

State

State is the data that changes while the app runs — the text in a search box, the items in a cart. Deciding which component owns each piece of state is the heart of React design.

Beginner 6 min Lesson 8 of 42Examples target React 19 with Vite.
Before you start:Props
0/42 in React
On this page · 6 min · Beginner

Learning objectives#

  • Tell state apart from props and derived values
  • Keep state minimal
  • Lift state up to share it between siblings
  • Follow one-way data flow

Explanation#

Props come from the parent and are read-only. State belongs to a component and changes over time (with useState or useReducer). Changing state re-renders the component and its children.

Keep only the minimum in state. Anything you can compute from props or other state — totals, filtered lists, full names — is derived: calculate it during render.

If two siblings need the same data, move (lift) the state up to their closest common parent and pass it down as props, together with functions to change it.

Each piece of state has one owner — a single source of truth. Children receive values and callbacks; they never hold copies that can drift out of sync.

Not everything is state: constants belong outside the component, and values that never affect rendering can live in a ref.

Syntax#

JSX Illustrative
function Parent() {
  const [query, setQuery] = useState('');           // state lives here
  return (
    <>
      <SearchBox query={query} onChange={setQuery} /> {/* writes */}
      <Results query={query} />                       {/* reads */}
    </>
  );
}

Code example: Lifting state up for a filterable list#

JSXFilterableList.jsx Runnable
import { useState } from 'react';


const PRODUCTS = [
  { id: 1, name: 'Pen', price: 20 },
  { id: 2, name: 'Pencil', price: 10 },
  { id: 3, name: 'Notebook', price: 60 },
];


function SearchBox({ query, onQueryChange }) {
  return (
    <input
      value={query}
      onChange={e => onQueryChange(e.target.value)}
      placeholder="Search"
      aria-label="Search products"
    />
  );
}


function ResultList({ items }) {
  return <ul>{items.map(p => <li key={p.id}>{p.name} — ₹{p.price}</li>)}</ul>;
}


export default function FilterableList() {
  const [query, setQuery] = useState('');                              // the only state


  const visible = PRODUCTS.filter(p =>                                 // derived, not stored
    p.name.toLowerCase().includes(query.toLowerCase()));
  const total = visible.reduce((sum, p) => sum + p.price, 0);         // derived


  return (
    <>
      <SearchBox query={query} onQueryChange={setQuery} />
      <ResultList items={visible} />
      <p>{visible.length} items, ₹{total} in total</p>
    </>
  );
}
Expected output
Initially all three products are listed with “3 items, ₹90 in total”. Typing “pen” shows Pen and Pencil with “2 items, ₹30 in total”. Both the list and the summary always agree because they come from the same query.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. query is the only state; it lives in the parent, FilterableList.
  2. SearchBox displays query and reports changes through onQueryChange — it owns nothing.
  3. visible and total are calculated during render, so they can never be out of date.
  4. PRODUCTS never changes, so it is a constant outside the component.

Real-world use case#

On a flight-search page, the date picker, passenger selector and results list all need the same search criteria. The criteria live in the page component and flow down as props.

Common mistakes#

Storing derived data in state

Keeping filteredItems in state means updating it everywhere query or items change — and forgetting once causes bugs. Compute it.

Copying props into state

useState(props.value) only uses the first value; later prop changes are ignored. Use the prop directly or lift the state.

State too high

Putting every input’s text in App re-renders the whole app on each keystroke. Keep state as close as possible to where it is used.

Best practices#

  • Minimal state, maximum derivation
  • One owner per piece of state
  • Lift state only as high as needed
  • Pass setters or callbacks down, values down

Remember this#

Props = given to you. State = yours and changing.

If you can calculate it, do not store it.

Siblings share state → lift it to the parent.

Practice exercise#

Your task: Two components, TemperatureInput (Celsius) and FahrenheitDisplay, must stay in sync. Lift the Celsius state into a parent and show both.

Need a hint?
Parent: const [celsius, setCelsius] = useState(25); pass celsius and setCelsius to the input, celsius to the display.
Show solution
JSX Runnable
import { useState } from 'react';


function TemperatureInput({ celsius, onChange }) {
  return <input type="number" value={celsius} onChange={e => onChange(Number(e.target.value))} aria-label="Celsius" />;
}


function FahrenheitDisplay({ celsius }) {
  return <p>{celsius}°C = {celsius * 9 / 5 + 32}°F</p>;
}


export default function Converter() {
  const [celsius, setCelsius] = useState(25);
  return (
    <>
      <TemperatureInput celsius={celsius} onChange={setCelsius} />
      <FahrenheitDisplay celsius={celsius} />   {/* 25°C = 77°F */}
    </>
  );
}

Mini quiz#

State quizMultiple choice Question 1 of 3

A cart total can be computed from the cart items. Where should it live?

Score so far: 0/0

Want more? Practise all React MCQs · React interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.