React Best Practices

Implementing React best practices for automated code quality and scalable frontend architecture integration

React Best Practices is a community skill for writing production-quality React applications, covering component design, hooks patterns, state management, performance optimization, and code organization for maintainable React codebases.

What Is This?

Overview

React Best Practices provides patterns for building well-structured React applications. It covers component design with single-responsibility components, composition over inheritance, and prop interface design, hooks patterns including custom hooks extraction, dependency management in useEffect, and controlled versus uncontrolled inputs, state management choices between useState, useReducer, Context, and external stores based on complexity, performance optimization with memo, useMemo, useCallback, and React Compiler for automatic memoization, and code organization with feature-based folder structure, barrel exports, and colocation principles. The skill enables developers to write React code that is maintainable, performant, and consistent across teams.

Who Should Use This

This skill serves React developers establishing coding standards for team projects, engineers optimizing React application performance and render cycles, and teams adopting modern React patterns with hooks and Server Components.

Why Use It?

Problems It Solves

Components that mix data fetching, business logic, and rendering become difficult to test and reuse. Improper useEffect dependencies cause stale closures or infinite re-render loops. State management without clear patterns leads to prop drilling or unnecessary global state. Missing memoization in render-heavy components causes performance degradation.

Core Highlights

Composition patterns keep components focused on single responsibilities. Custom hooks extract reusable stateful logic from components. Controlled state patterns prevent data synchronization bugs. Performance patterns reduce unnecessary re-renders in complex UIs.

How to Use It?

Basic Usage

import { useState, useCallback }
  from 'react';

// Custom hook extraction
function useSearch<T>(
  items: T[],
  filterFn: (
    item: T,
    query: string
  ) => boolean
) {
  const [query, setQuery] =
    useState('');

  const filtered =
    query.trim()
      ? items.filter(
          (item) =>
            filterFn(
              item, query))
      : items;

  return {
    query,
    setQuery,
    filtered,
    count: filtered.length,
  };
}

// Component using hook
interface User {
  id: string;
  name: string;
  email: string;
}

function UserList({
  users }: {
    users: User[] }) {
  const { query, setQuery,
    filtered } =
      useSearch(users,
        (user, q) =>
          user.name
            .toLowerCase()
            .includes(
              q.toLowerCase()));

  return (
    <div>
      <input
        value={query}
        onChange={(e) =>
          setQuery(
            e.target.value)}
        placeholder="Search" />
      <ul>
        {filtered.map(
          (user) => (
            <li key={user.id}>
              {user.name}
            </li>
          ))}
      </ul>
    </div>
  );
}

Real-World Examples

import { useReducer }
  from 'react';

// useReducer for complex state
interface FormState {
  values: Record<
    string, string>;
  errors: Record<
    string, string>;
  submitting: boolean;
}

type FormAction =
  | { type: 'SET_FIELD';
      field: string;
      value: string }
  | { type: 'SET_ERROR';
      field: string;
      error: string }
  | { type: 'SUBMIT' }
  | { type: 'SUBMIT_DONE' };

function formReducer(
  state: FormState,
  action: FormAction
): FormState {
  switch (action.type) {
    case 'SET_FIELD':
      return {
        ...state,
        values: {
          ...state.values,
          [action.field]:
            action.value },
        errors: {
          ...state.errors,
          [action.field]: '' },
      };
    case 'SET_ERROR':
      return {
        ...state,
        errors: {
          ...state.errors,
          [action.field]:
            action.error },
      };
    case 'SUBMIT':
      return {
        ...state,
        submitting: true };
    case 'SUBMIT_DONE':
      return {
        ...state,
        submitting: false };
    default:
      return state;
  }
}

function useForm() {
  const [state, dispatch] =
    useReducer(formReducer, {
      values: {},
      errors: {},
      submitting: false,
    });
  return { state, dispatch };
}

Advanced Tips

Use React Compiler (React Forget) to eliminate manual useMemo and useCallback for automatic memoization. Colocate related files in feature folders rather than grouping by type. Prefer composition with children and render props over deeply nested component hierarchies.

When to Use It?

Use Cases

Build a searchable list with a custom useSearch hook that separates filter logic from presentation. Implement a multi-step form with useReducer for structured state transitions. Optimize a dashboard with memoized chart components that only re-render on data changes.

Related Topics

React hooks, component composition, state management, memoization, and code organization.

Important Notes

Requirements

React 18 or later for concurrent features and automatic batching. TypeScript recommended for typed component props and hook interfaces. ESLint with eslint-plugin-react-hooks for dependency checking.

Usage Recommendations

Do: extract custom hooks when stateful logic is shared across two or more components. Use useReducer for state with multiple sub-values or complex transitions. Keep components under 150 lines by extracting subcomponents.

Don't: wrap every component in React.memo without profiling to confirm a rendering bottleneck. Create useEffect for derived state that computed values would handle. Pass unstable object literals as props causing unnecessary child re-renders.

Limitations

React Compiler requires specific toolchain setup and may not optimize all patterns. Custom hooks cannot enforce usage rules at the type level requiring documentation. Performance optimization adds complexity and should only be applied where profiling shows actual bottlenecks.