Why? Minimises re‑render surface and keeps ownership obvious.
When you’re new to React it’s tempting to stash all your state in a parent component “just to be safe.” Unfortunately, every time that state changes, everything below it re‑renders—even parts that don’t care. This slows things down, hides the real owner of the data, and makes future refactors painful. By keeping state close to where it’s used, each update only touches the components that actually need to react, which means faster screens and cleaner code.
import { useEffect, useState } from "react";
type Todo = { id: number; text: string };
export default function TodoList() {
// Server‑fetched data belongs here: multiple items depend on it.
const [todos, setTodos] = useState<Todo[]>([]);
useEffect(() => {
fetch("/api/todos")
.then((res) => res.json())
.then(setTodos);
}, []);
return (
<ul className="space-y-2">
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
function TodoItem({ todo }: { todo: Todo }) {
// Checkbox state is local—only this row cares.
const [checked, setChecked] = useState(false);
return (
<li className="flex items-center gap-2">
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<span className={checked ? "line-through" : ""}>{todo.text}</span>
</li>
);
}
// Hoisting every item’s checkbox state to the top component.
function TodoApp() {
const [todos, setTodos] = useState<Todo[]>([]);
const [checkedMap, setCheckedMap] = useState<Record<number, boolean>>({});
useEffect(() => {
fetch("/api/todos")
.then((res) => res.json())
.then(setTodos);
}, []);
const toggle = (id: number) =>
setCheckedMap((prev) => ({ ...prev, [id]: !prev[id] }));
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={!!checkedMap[todo.id]}
onChange={() => toggle(todo.id)}
/>
{todo.text}
</li>
))}
</ul>
);
}
// A single checkbox flips? The whole list (and any unrelated siblings)
// still re-renders. Performance tanks and intent is murky.