Frontend Components and React Basics
Build component based UIs with React, manage state, effects, and routing, and connect to Flask APIs.
Content
State and setState
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
State and setState — The Beating Heart of Interactive React UIs
"Props tell a component what it is. State tells a component who it is right now."
You're already comfortable with components, props, and JSX syntax — you know how to build a component and pass data down. You probably also just fetched JSON from an API and wondered how to show those results in the UI. Enter state. This is where your UI remembers things, reacts to user input, and updates when data arrives from that API you fetched.
What is state in React? (Short, spicy version)
- State = data that a component owns and that affects rendering.
- It's local (to the component) and mutable (you change it), but React controls updates so the DOM stays in sync.
- Props are external inputs. State is internal memory.
Why it matters: without state your UI is static — you couldn't show loading spinners, typed input, toggles, counters, or API data that arrives later.
setState: two flavors (class components vs hooks)
React historically offered state via class components with setState. Modern React mostly uses hooks (useState) in function components. Both let you update state, but they behave differently.
Class components: this.setState
class Todos extends React.Component {
constructor(props) {
super(props);
this.state = { todos: [], loading: true };
}
componentDidMount() {
fetch('/api/todos')
.then(res => res.json())
.then(data => this.setState({ todos: data, loading: false }));
}
addTodo(newTodo) {
// Do not mutate this.state.todos directly
this.setState(prevState => ({
todos: [...prevState.todos, newTodo]
}));
}
render() {
// use this.state.todos
}
}
Key notes:
this.setState(updates)merges updates into the current state (shallow merge).setStatecan be asynchronous and may be batched for performance. That means you can't reliably readthis.stateimmediately after calling setState.- Use the updater form
this.setState(prev => next)when new state depends on previous state.
Function components: useState
function Counter() {
const [count, setCount] = useState(0);
function increment() {
// safe when relying on previous value
setCount(prev => prev + 1);
}
return <button onClick={increment}>Clicked {count} times</button>;
}
Key notes:
useStatereplaces the state variable (no merge). If you have an object state, you must merge manually.setCountalso may be asynchronous; use functional updates (prev => ...) when relying on previous values.
Real-world analogy: state as a sticky note on a fridge
- Props are the recipe you borrowed from a friend: read-only.
- State is your sticky note where you jot down modifications as you cook — how spicy you like it today. You update the sticky note (setState), then the kitchen rearranges accordingly (re-render).
This fridge note can be updated by callbacks (user types, API returns data), and multiple updates can be squashed together by React so the kitchen doesn't do unnecessary flips.
Common patterns & pitfalls (with quick fixes)
- Direct mutation — the cardinal sin
Bad:
this.state.items.push(newItem);
this.setState({ items: this.state.items });
Good:
this.setState(prev => ({ items: [...prev.items, newItem] }));
Why: React relies on new references to detect changes. Mutating the existing array/object breaks that.
- Relying on state immediately after setState
Bad:
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // might still be old
Fix: Use callback in class components or useEffect in functional ones.
Class callback form:
this.setState({ count: newCount }, () => {
// safe to read this.state.count here
});
Functional components:
setCount(n => n + 1);
// to act after the DOM updates, use useEffect with the state as dependency
useEffect(() => {
// runs after count changes
}, [count]);
- Stale closures in event handlers
If you capture a value in a closure (e.g., in an effect or handler) and later call setState assuming a current value, you can get stale updates. Solution: use functional updates setState(prev => ...).
- Forgetting to merge when using objects with useState
With class setState, updates are shallow-merged. With useState, a state setter replaces the value:
const [state, setState] = useState({ a: 1, b: 2 });
// Oops - overwrites b
setState({ a: 9 });
// Better
setState(prev => ({ ...prev, a: 9 }));
When fetching data from APIs (you remember the async lesson)
You fetch JSON from a server; the data arrives later. That means you use state to hold loading/data/error and update it when the promise resolves.
Functional example:
function UsersList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let mounted = true;
fetch('/api/users')
.then(r => r.json())
.then(data => { if (mounted) { setUsers(data); setLoading(false); } })
.catch(() => { if (mounted) { setLoading(false); } });
return () => { mounted = false; };
}, []);
if (loading) return <p>loading…</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Tips:
- Use a
loadingboolean in state to show spinners. - Cancel or ignore responses when the component unmounts to avoid setting state on unmounted components.
- Use functional updates when updating arrays/objects based on previous state.
Quick comparison table
| Feature | class this.setState | function useState |
|---|---|---|
| Merge behavior | Shallow merge of object | Replaces value; manual merge needed |
| API | this.setState(obj or fn) | const [s, setS] = useState(); setS(val or fn) |
| Callback after set | Optional second argument | useEffect for side effects |
| Typical in | Older / class codebases | Modern React / function components |
Mini checklist before you call setState
- Am I mutating the current state? If yes, stop.
- Does my update depend on previous state? Use functional updater.
- Could this update cause unnecessary renders? Consider batching or combining updates.
- If fetching async data, handle component unmounts.
Key takeaways (so they stick)
- State is local, mutable data that drives UI. Props are inputs; state is inner memory.
- setState is asynchronous and may be batched. Use functional updates when you depend on previous state.
- Class setState shallow-merges; hooks replace. If you use an object with useState, spread the previous state.
- Never mutate state directly. Always create new arrays/objects for updates.
"If state is the heart of your component, setState is its heartbeat. Treat it kindly — don’t poke it with mutations."
If you want, I can make a tiny interactive sandbox example (JSX) that shows: a) API fetch, b) controlled input, and c) a counter demonstrating functional updates — all in one component so you see state and setState behave together. Want that?
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!