Keep Components Small and Focused
A component that renders a huge block of JSX and manages many unrelated pieces of state is hard to reason about and reuse. Splitting it into smaller components — each with a clear, single responsibility — keeps the codebase easier to navigate as it grows.
General Guidelines
- Keep state as close as possible to where it’s used; lift it up only as far as necessary.
- Prefer composition (children, named slots) over deeply configurable, prop-heavy components.
- Derive values during render instead of storing them in extra state.
- Follow the Rules of Hooks — only call hooks at the top level, only from components or custom hooks.
- Extract a custom hook as soon as stateful logic is duplicated across components.
- Always provide a stable, unique key when rendering a list.
Don’t Store Derivable Values in State
If a value can be computed directly from existing props or state during render, storing it in its own state variable just creates another thing that can get out of sync.
Derived Value vs Redundant State
// Avoid: fullName can silently get out of sync with firstName/lastName
const [fullName, setFullName] = useState("");
// Prefer: always correct, computed fresh every render
const fullName = `${firstName} ${lastName}`;Handle Loading, Error, and Empty States Explicitly
Every component that depends on async data has at least three states beyond "happy path with data": loading, error, and empty. Skipping any of them tends to produce confusing UI (a blank screen, or a crash) in real-world conditions like a slow network or failed request.
Handling All the States
function UserList({ users, loading, error }) {
if (loading) return <p>Loading...</p>;
if (error) return <p>Something went wrong.</p>;
if (users.length === 0) return <p>No users found.</p>;
return (
<ul>
{users.map((u) => <li key={u.id}>{u.name}</li>)}
</ul>
);
}Avoid Overusing useEffect
A very common source of bugs is using useEffect to synchronize state that could instead be computed directly during render, or updated directly inside the event handler that caused the change. If an effect exists just to react to a state change you caused yourself, it usually doesn’t need to be an effect at all.
Best Practice
Reach for the simplest tool that solves the problem: plain JavaScript expressions before conditional rendering helpers, useState before useReducer, local state before Context, Context before an external library. Add complexity only once a real, felt need justifies it.
Summary
Great React code isn’t about knowing every advanced hook — it’s about small, focused components, state that lives at the right level, and reaching for more powerful tools (Context, reducers, external libraries) only once simpler ones genuinely stop being enough.