React State
State is data that can change over time and, when it changes, causes the component to re-render with the new data. State is private โ it belongs to the component that declares it.
useState Basics
import { useState } from "react";
function Counter() {
// useState(initialValue) returns [currentValue, setterFunction]
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>โ</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
State vs Props
| Props | State | |
|---|---|---|
| Source | Passed from parent | Owned by component |
| Mutable? | No โ read only | Yes โ via setter |
| Triggers re-render? | Yes (when parent re-renders) | Yes (when set) |
| Visible to parent? | Yes | No (unless lifted up) |
Updating Object State
function ProfileForm() {
const [profile, setProfile] = useState({
name: "",
email: "",
bio: "",
});
const handleChange = (e) => {
// ALWAYS spread previous state โ never mutate directly
setProfile(prev => ({
...prev,
[e.target.name]: e.target.value, // Computed property name
}));
};
return (
<form>
<input name="name" value={profile.name} onChange={handleChange} />
<input name="email" value={profile.email} onChange={handleChange} />
<textarea name="bio" value={profile.bio} onChange={handleChange} />
</form>
);
}
Updating Array State
function TodoList() {
const [todos, setTodos] = useState([]);
// Add โ spread + new item
const add = (text) =>
setTodos(prev => [...prev, { id: Date.now(), text, done: false }]);
// Remove โ filter out
const remove = (id) =>
setTodos(prev => prev.filter(t => t.id !== id));
// Toggle โ map + spread
const toggle = (id) =>
setTodos(prev => prev.map(t =>
t.id === id ? { ...t, done: !t.done } : t
));
// Clear all
const clear = () => setTodos([]);
}
Lifting State Up
When two sibling components need to share state, lift it to their nearest common ancestor and pass it down via props.
function App() {
// State lives here โ shared between SearchBar and ResultsList
const [query, setQuery] = useState("");
return (
<>
<SearchBar query={query} onQueryChange={setQuery} />
<ResultsList query={query} />
</>
);
}
function SearchBar({ query, onQueryChange }) {
return <input value={query} onChange={e => onQueryChange(e.target.value)} />;
}
WarningNever modify state directly:
todos.push(item) โ. Always use the setter: setTodos([...todos, item]) โ
. Direct mutation does not trigger a re-render.