DevAcademy
LearnReactReact Best Practices
AdvancedReact

React Best Practices

Learn conventions and habits for writing maintainable, scalable React applications.

Reading Time

16 min

Lesson

Lesson 42 of 42

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.

Interview Questions

Quick Quiz

1. Why avoid storing a value in state if it can be computed from existing props/state?

2. What three states should most components fetching async data handle explicitly?

3. What is a common sign that a useEffect might be unnecessary?

Previous