State
By the end of this lesson you'll be able to give a component its own memory with useState , tell state apart from props, update objects and arrays the way React requires (without ever breaking re-rendering), and share one piece of state between components by lifting it up.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
State is a component's memory . Think of a phone's screen-brightness slider. The current brightness is something the phone remembers — that's state. Each time you slide it, the phone updates the stored value and redraws the screen to match. Props , by contrast, are like the phone model printed on the box: handed to the device from outside and never changed by the device itself. In React, when state changes the component "redraws" (re-renders); when you mutate a value without telling React, nothing redraws — like changing the brightness number in a spec sheet and expecting the screen to dim.
1. useState — A Component's Memory
useState is a React Hook (a special function whose name starts with use ) that gives a component a value it can remember between renders. You call it like : the argument 0 is the initial value , and you get back a pair — the current value ( count ) and a setter function ( setCount ). The one rule that makes React work: only the setter changes state, and calling the setter is what tells React to re-render . Read this worked example, run it, and watch the setter trigger a "render".
In a real component this all lives inside the function. Here's the JSX it produces (read-only — the type annotation useState number pins the value to a number):
Every click calls setCount , React stores the new number, then re-runs Counter so the button text updates. That loop — event → setter → re-render — is the heartbeat of every interactive React app.
2. State vs Props
Beginners constantly confuse these two, so burn in the difference now. Props are passed into a component by its parent and are read-only — the component must never reassign them. State is owned by the component and is the only thing it's allowed to change (through its setter). A handy test: "Could this value ever change while the component is on screen, because of something the component itself does?" If yes, it's state; if it just arrives from outside, it's a prop.
3. Updating Objects Immutably
When state holds an object, you must never edit it in place ( obj.age = 29 ). React decides whether to re-render by checking if the state is a new object — same object reference, no re-render. So you build a fresh copy with the spread operator ... , then override just the field you're changing: {"setUser( )"} . The spread copies every existing field; the field you list after it wins. Your turn — fill the blank.
4. Updating Arrays Immutably
Arrays follow the same rule: produce a new array , never mutate the old one. That means avoiding push , pop , splice and sort (they change the array in place) and reaching instead for operations that return a new array: spread to add ( ), filter to remove , and map to change one item . Fill in the two blanks below.
5. Functional Updates —
When the new state is built from the old state, pass the setter a function instead of a value: . React calls your function with the very latest value, so you can never accidentally work from a stale (outdated) snapshot — which is exactly what goes wrong if you fire several updates in a row using a plain variable. Run this to see the trap and the fix side by side.
Once updates get complex, teams centralise them in a single reducer — a pure function (state, action) => newState that always returns a brand-new state object and never mutates the old one (this is the idea behind useReducer and Redux). Notice every case below uses spread for the object and for the array, so the original is left untouched.
6. Lifting State Up
What if two components need the same piece of data — say one shows a count and another changes it? If each keeps its own copy, they drift out of sync. The fix is lifting state up : move the state to the nearest common parent , then pass the value down as a prop to one child and the setter down to the other. Now there is a single source of truth , and every child reads the same value.
📋 Quick Reference
Q: Why doesn't my UI update when I change a variable directly?
Because React only re-renders when you call a setter . Assigning to a plain variable (or mutating state in place) changes the value but never tells React to redraw, so the screen stays stale. Always go through setState .
Q: What's the real difference between state and props?
Props are passed in from the parent and are read-only inside the component. State is owned by the component and is the only data it may change (via its setter). If a value can change because of what the component itself does, it's state.
Whenever the next value is calculated from the current one — counters, toggles, appending to a list. The functional form always receives the latest state, so multiple updates in a row don't clobber each other.
Yes — just update it immutably. Build a new object/array with spread ( ... ), map , or filter and pass that to the setter; never push, splice, or assign to a field of the existing one.
No blanks this time — just a brief and an outline. Write a toggle function that flips a "liked" flag and keeps an accurate count, using only immutable updates. Run it and check your output against the expected line in the comments. This is exactly the logic behind a real like button.
Practice quiz
What does const [count, setCount] = useState(0) return?
- Two copies of the same value
- An object with a value property
- A pair: the current value and a setter function
- The initial value only
Answer: A pair: the current value and a setter function. useState returns an array of two things: the current state value and a setter that updates it and triggers a re-render.
What actually causes React to re-render after a state change?
- Calling the setter function returned by useState
- Assigning directly to the state variable
- Mutating a property of the state object
- Any console.log call
Answer: Calling the setter function returned by useState. Only calling the setter tells React to store the new value and re-run the component. A plain assignment changes the value but never re-renders.
What is the key difference between props and state?
- Props are numbers; state is objects
- State is passed in by the parent; props are owned locally
- There is no difference
- Props are passed in by the parent and are read-only; state is owned by the component and changed via its setter
Answer: Props are passed in by the parent and are read-only; state is owned by the component and changed via its setter. Props arrive from outside and must never be reassigned inside the component. State belongs to the component and is the only data it may change.
How do you correctly update one field of an object held in state?
- user.age = 29
- setUser({ ...user, age: 29 })
- setUser({ age: 29 })
- user = { ...user, age: 29 }
Answer: setUser({ ...user, age: 29 }). Spread the old object to copy every field, then override the one you're changing. Mutating in place keeps the same reference so React sees no change.
Why does setUser({ age: 29 }) cause a bug when state is { name, age, city }?
- It replaces the whole object, throwing away name and city
- It throws a syntax error
- It mutates the original object
- It only works on arrays
Answer: It replaces the whole object, throwing away name and city. Passing a fresh object replaces state entirely. You must spread first ({ ...user, age: 29 }) to keep the other fields.
Which array operation is safe to use when updating state immutably?
- todos.push(item)
- todos.splice(0, 1)
push, splice, and sort mutate in place. Use spread to add, filter to remove, and map to change an item — each returns a brand-new array.
When should you use a functional update like setCount(prev => prev + 1)?
- Never; it's deprecated
- Whenever the new state is calculated from the old state
- Only inside useEffect
- Only for string state
Answer: Whenever the new state is calculated from the old state. The functional form receives the latest value, so multiple updates in a row don't clobber each other by reusing a stale snapshot.
What happens if you fire setCount(count + 1) three times in one handler using a plain count?
- Count goes up by 3
- React throws an error
- Count goes up by 2
- Count goes up by only 1 because each call reuses the same stale count
Answer: Count goes up by only 1 because each call reuses the same stale count. All three reads see the same old count, so the result is +1. Use setCount(prev => prev + 1) to get +3.
What does "lifting state up" mean?
- Moving state into a global variable
- Moving shared state to the nearest common parent and passing it down as props
- Storing state in localStorage
- Using more useState calls
Answer: Moving shared state to the nearest common parent and passing it down as props. When two components need the same data, the parent owns it as a single source of truth and passes the value (and setter) down, keeping siblings in sync.
Why does the 'Invalid hook call' error occur with useState?
- Because you used too many useState calls
- Because the initial value was null
- Because a hook was called in a loop, condition, or non-component function instead of the top level
- Because you forgot to import React
Answer: Because a hook was called in a loop, condition, or non-component function instead of the top level. Hooks must be called at the top level of a component in the same order every render — not inside loops, conditions, or ordinary functions.