State in React
By Syncfusion®, Inc. · Last updated
Local component state with useState: state vs props, reading and updating state, state-driven rendering, updating from the previous value, common state values, immutable updates of objects and arrays, and when to use state.
Introduction to Local Component State
State is data that a component remembers and can change. When state changes, React shows the new value on the screen.
It is called local state because it belongs to one component. Other components cannot see it unless you send it to them as props.
Real-World Example
In a bookshop app, the cart button shows "Cart (0)". When the user clicks Add to cart, the component calls setCount(count + 1).
React stores the new count and renders the component again, so the button shows "Cart (1)". The developer never changes the page text directly.
State is data that a component remembers. When state changes, React updates the screen.
State Compared with Props
Props and state are both data, but they come from different places.
| Props | State | |
|---|---|---|
| Where it comes from | Sent by the parent | Created inside the component |
| Who can change it | Only the parent | The component itself |
| Can the component change it? | No, it is read-only | Yes, with its set function |
| What happens on change | The child shows the new props | The component shows the new state |
The two often work together. A parent keeps a value in its state and sends it to a child as a prop.
App owns the state count. It sends count to CartBadge as a prop. When you click, App changes its state, and CartBadge gets the new number.
Creating State with useState
To create state, you use a function from React called useState.
First, import it at the top of your file:
import { useState } from "react";
Then, inside your component, write this line:
const [count, setCount] = useState(0);
This line creates one piece of state. It has three parts:
The three parts of const [count, setCount] = useState(0): count is the value, setCount changes it, 0 is the starting value
const [count, setCount] = useState(0);
The value now The function to change it The starting value
One line, three parts.
countholds the value now. At the start, it is0.setCountis a function. You call it to changecount.0is the starting value. It is used only the first time.
You can give the state any name you like. But there is a common naming pattern that most people follow:
- Give the value a simple name, like
count. - Give the function the same name with
setat the start, likesetCount.
Typing State
Every piece of state has a type, such as a number or text. Most of the time, TypeScript finds the type by itself from the starting value:
const [count, setCount] = useState(0); // 0 is a number, so count is a number
const [name, setName] = useState(""); // "" is text, so name is text
const [isOpen, setIsOpen] = useState(false); // false is true/false, so isOpen is a boolean
Sometimes the starting value is not enough. For example, an empty list [] does not tell TypeScript what kind of items will go in it.
In that case, write the type yourself, inside < > after useState:
const [books, setBooks] = useState<string[]>([]);
<string[]> means "a list of text". Now TypeScript knows that books holds text items, and it warns you if you try to add a number.
Call useState at the top of your component, not inside an if or a loop. React needs to find each piece of state in the same order every time.
Reading State
To read state, use its variable, like any other variable. You can show it in TSX, use it in maths, or use it in a condition.
<p>Books in cart: {count}</p>
<p>Total: ₹{count * 299}</p>
{count === 0 && <p>Your cart is empty.</p>}
Updating State
To change state, call its set function with the new value. Do not change the variable directly.
The state update cycle: click, setCount, React runs the component again, the screen shows the new value
1. You click
2. setCount(1)
3. React re-renders
4. Screen shows 1
Every state update goes round this cycle. "Re-render" means React runs the component again.
When you call setCount(1), React does three things:
- It saves the new value.
- It runs your component function again. This is called a re-render.
- It updates only the parts of the screen that changed.
Why Not Change the Variable Directly?
If you write count = count + 1, the number changes in memory, but React does not know. It does not re-render, so the screen stays the same. Only the set function tells React to update the screen.
State-Driven Rendering
State-driven rendering means the screen is decided by the state. You do not change the page yourself. You change the state, and React shows the matching screen.
showDetails controls two things: the button text and the details paragraph. !showDetails flips true to false, and false to true.
Updating State from Previous State
The set function does not change the state at once. React waits until your click code has finished, and then re-renders. So inside one click, count still holds the old value.
This causes a problem when you update the same state more than once. Try both buttons below.
"+3 (wrong)" adds only 1. "+3 (right)" adds 3.
The right way passes a function to setCount. React gives that function the newest value as prev, and the function returns the next value. So each line builds on the line before it.
When the new value depends on the old value, write setCount((prev) => prev + 1).
Common State Values
State can hold any kind of value. These are the most common ones:
| Kind of value | Example | Used for |
|---|---|---|
| Number | useState(0) | Counters, quantities, scores |
| Text | useState("") | What the user types in a box |
| True or false | useState(false) | Show or hide, open or closed |
| Array | useState<string[]>([]) | A list of items, such as a cart |
| Object | useState({ name: "", email: "" }) | A group of related values, such as a form |
Type in the box. Each key press calls setName with the new text, and the greeting updates at once. e.target.value is the text that is in the box now.
Updating Objects and Arrays Immutably
Immutable means "not changed". In React, you never change an object or array that is in state. You make a new copy with the change, and pass the copy to the set function.
React compares the old value and the new value. If you change the old one and pass it back, React sees the same object and does not update the screen.
Animation: the old array stays the same; its items are copied into a new array, the new item is added, and the new array is passed to setBooks
Old array: booksNew array: [...books, new] The AlchemistWings of Fire
...books The Alchemist Wings of Fire + Atomic Habits (new) ✓ not changed setBooks(newArray) React sees a new array and updates the screen
Make a new array with the old items plus the new one. The old array is left as it was.
The Spread Operator
The spread operator ... copies all the items of an array, or all the fields of an object, into a new one.
// Arrays
setBooks([...books, "Atomic Habits"]); // add an item
setBooks(books.filter((b) => b !== "Wings of Fire")); // remove an item
// Objects
setReader({ ...reader, city: "Chennai" }); // change one field
[...books, "Atomic Habits"]makes a new array with all old books plus the new one.filtermakes a new array without the removed item.{ ...reader, city: "Chennai" }makes a new object with all old fields, then changescity.
Now try the wrong way and the right way side by side.
Press "Add (wrong)". The console says the array grew, but the list on the screen did not change. Now press "Add (right)". The list updates, and it also shows the books that the wrong button added.
Updating an Object
Each button makes a new object. ...reader copies name, city and points. The field written after it replaces the old value.
When to Use State
Ask one question: does this value change while the app is running, and must the screen show the change? If yes, use state.
- Use state for things the user changes: a counter, typed text, an open or closed menu, the items in a cart.
- Do not use state for values that never change, such as a shop name. Use a normal variable.
- Do not use state for values you can work out from other state. If you have
books, the count is justbooks.length. You do not need a second state for it. - Do not use state for data that comes from the parent. Use props.
Local State Best Practices
- Keep state as small as you can. Store only what you need, and work out the rest.
- Keep state in the component that uses it. If two components need the same state, keep it in their shared parent and pass it down as props.
- Give clear names. Use
isOpenandsetIsOpen, notxandsetX. - Use the function form
setCount((prev) => prev + 1)when the new value depends on the old one. - Always make copies of objects and arrays. Never use
pushor change a field directly.
Common mistakes
| Mistake | What you see | Fix |
|---|---|---|
Changing state directly: count = count + 1 | The screen does not change | Use the set function: setCount(count + 1) |
Using push on an array in state | The screen does not change | Make a new array: setBooks([...books, item]) |
Changing an object field: reader.city = "Chennai" | The screen does not change | Make a new object: setReader({ ...reader, city: "Chennai" }) |
Calling setCount(count + 1) several times in one click | The value goes up by only 1 | Use setCount((prev) => prev + 1) |
Calling useState inside an if or a loop | React shows an error | Call useState at the top of the component |
| Reading state right after setting it | You get the old value | Use the new value directly, or read it on the next render |
Calling the set function during render: {setCount(1)} | Too many re-renders error | Call set functions only in events, such as onClick |
Forgetting to import useState | useState is not defined | Add import { useState } from "react"; |
Check Yourself
Exercises
What to Remember
- State is data a component remembers. Create it with
useState, and change it only with its set function. - When state changes, React re-renders and the screen shows the new value.
- Use
prev => ...when the new value depends on the old one, and always copy objects and arrays.
Next: Event Handling