# React Development Best Practices

## A practical guide with good and bad examples

**Purpose:** A practical reference for building React applications that are easy to understand, test, maintain, and scale.

**The best React code is not the code that uses the most Hooks or libraries. It is code that solves the requirement clearly, safely, and with the least unnecessary complexity.**

**Audience:** Beginner-to-intermediate React developers and teams reviewing production code.

**Examples:** Most examples use modern function components, JavaScript, and Vite-style environment variables. Add imports where needed. Adapt the folder structure to the size of your application rather than following it mechanically.

---

## Contents

1. [Core principles](#1-core-principles)
2. [Component design](#2-component-design)
3. [File naming and folder structure](#3-file-naming-and-folder-structure)
4. [Props and component communication](#4-props-and-component-communication)
5. [State management fundamentals](#5-state-management-fundamentals)
6. [useState best practices](#6-usestate-best-practices)
7. [useEffect best practices](#7-useeffect-best-practices)
8. [Other important Hooks](#8-other-important-hooks)
9. [Custom Hooks](#9-custom-hooks)
10. [Context API and useContext](#10-context-api-and-usecontext)
11. [useReducer](#11-usereducer)
12. [Choosing useState, useReducer, Context, or Redux Toolkit](#12-choosing-usestate-usereducer-context-or-redux-toolkit)
13. [Redux Toolkit](#13-redux-toolkit)
14. [API calls and data fetching](#14-api-calls-and-data-fetching)
15. [Loading, error, empty, and success states](#15-loading-error-empty-and-success-states)
16. [Forms and validation](#16-forms-and-validation)
17. [Rendering and performance](#17-rendering-and-performance)
18. [Routing and code splitting](#18-routing-and-code-splitting)
19. [Error boundaries and error handling](#19-error-boundaries-and-error-handling)
20. [Security and accessibility](#20-security-and-accessibility)
21. [Testing](#21-testing)
22. [Code quality and maintainability](#22-code-quality-and-maintainability)
23. [Example feature structure](#23-example-feature-structure)
24. [Code review checklist](#24-code-review-checklist)
25. [Useful official resources](#25-useful-official-resources)

---

## 1\. Core principles

Keep these principles in mind when writing React code:

- **Keep components focused:** A component should have one clear UI responsibility.
- **Keep state close to where it is used:** Do not make every value global.
- **Avoid duplicated state:** Derive values from existing state when possible.
- **Do not mutate state:** Create new arrays and objects when updating state.
- **Separate responsibilities:** UI, API communication, business logic, and shared utilities should not become one large file.
- **Handle every request state:** Consider loading, success, empty, and error states.
- **Optimize based on evidence:** Do not add memoization everywhere.
- **Make the UI accessible:** Prefer semantic HTML and keyboard-friendly interactions.
- **Test important behaviour:** Focus on user-visible outcomes and critical business rules.

React is declarative: describe what the UI should look like for the current state. Avoid manually changing the DOM when React state can represent the change.

### Bad: manually changing the DOM

```jsx
function Message() {
  function showMessage() {
    document.getElementById("message").innerText = "Saved";
  }

  return <p id="message">Not saved</p>;
}
```

### Good: represent the UI with state

```jsx
import { useState } from "react";

function Message() {
  const [saved, setSaved] = useState(false);

  return (
    <>
      <p>{saved ? "Saved" : "Not saved"}</p>
      <button onClick={() => setSaved(true)}>Save</button>
    </>
  );
}
```

---

## 2\. Component design

### 2.1 Keep components small and focused

A component should have a clear purpose. Split a large component when it handles unrelated UI, data fetching, form logic, filtering, and modal behaviour all together.

### Bad

```jsx
function UsersPage() {
  // Fetch users
  // Filter users
  // Handle form submission
  // Manage modal
  // Render header, filters, table and modal
  // Hundreds of lines of mixed responsibilities
}
```

### Good

```jsx
function UsersPage() {
  return (
    <PageLayout>
      <UsersHeader />
      <UserFilters />
      <UsersTable />
      <UserFormDialog />
    </PageLayout>
  );
}
```

Do not split every few lines into a new component. Extract a component when it has a meaningful responsibility, is reused, or makes the parent easier to understand.

### 2.2 Prefer function components

Use function components and Hooks for new React code.

```jsx
function Welcome({ name }) {
  return <h1>Welcome, {name}</h1>;
}
```

Class components are still supported, and you may encounter them in older projects. New application code generally uses function components.

### 2.3 Keep render logic readable

### Bad

```jsx
return (
  <div>
    {users && users.length > 0
      ? users.map((user) => user.active
          ? <div>{user.name}</div>
          : null)
      : <p>No users</p>}
  </div>
);
```

### Good

```jsx
if (users.length === 0) {
  return <p>No users found.</p>;
}

return (
  <ul>
    {users.map((user) => (
      <li key={user.id}>
        {user.name}
      </li>
    ))}
  </ul>
);
```

Use helper functions or smaller components when conditional rendering becomes complicated.

### 2.4 Avoid defining components inside components

Defining a component inside another component creates a new component type on every parent render and can reset child state.

### Avoid

```jsx
function Dashboard() {
  function ProfileCard() {
    return <div>Profile</div>;
  }

  return <ProfileCard />;
}
```

### Prefer

```jsx
function ProfileCard() {
  return <div>Profile</div>;
}

function Dashboard() {
  return <ProfileCard />;
}
```

---

## 3\. File naming and folder structure

Consistency matters more than one universally correct naming scheme. Agree on conventions and use them throughout the project.

### 3.1 Recommended file naming

---

Item Recommended convention Example

---

React component PascalCase `UserProfile.jsx`

Page component PascalCase `UsersPage.jsx`

Custom Hook camelCase with `use` `useUsers.js` prefix

Utility function camelCase `formatDate.js`

API/service module camelCase `userService.js`

Context PascalCase or `AuthContext.jsx` descriptive camelCase

Reducer camelCase `cartReducer.js`

Constants camelCase file; `appConstants.js` uppercase values where\
useful

Test Match source file plus `UserProfile.test.jsx` test suffix

## Styles Match component name `UserProfile.module.css`

Use names that describe purpose. Avoid vague names such as `data.js`, `common2.js`, `helper.js`, or `NewComponent.jsx`.

### 3.2 Small application structure

For a small app, a simple structure is enough:

```text
src/
├── assets/
├── components/
├── pages/
├── hooks/
├── services/
├── utils/
├── App.jsx
└── main.jsx
```

### 3.3 Feature-based structure for medium and large applications

Group code by feature so related UI, hooks, API functions, and tests stay together.

```text
src/
├── app/
│   ├── App.jsx
│   ├── routes.jsx
│   └── store.js
│
├── features/
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   ├── authSlice.js
│   │   └── AuthPage.jsx
│   │
│   ├── users/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   ├── usersSlice.js
│   │   └── UsersPage.jsx
│   │
│   └── products/
│       ├── components/
│       ├── hooks/
│       ├── services/
│       └── ProductsPage.jsx
│
├── shared/
│   ├── components/
│   ├── hooks/
│   └── utils/
│
├── services/
│   └── apiClient.js
│
├── assets/
├── styles/
└── main.jsx
```

Use `features/` for feature-specific code and `shared/` for genuinely reusable code. Do not move a component into `shared/` just because it is used by two files if it is still tightly coupled to one feature.

### 3.4 Avoid overengineering folders

Do not create many empty folders for a small project. Start simple and introduce feature boundaries as the application grows.

---

## 4\. Props and component communication

Props are the normal way for a parent to pass data and callbacks to a child.

### Good

```jsx
function UserCard({ user, onSelect }) {
  return (
    <button onClick={() => onSelect(user.id)}>
      {user.name}
    </button>
  );
}

function UsersList({ users }) {
  function handleSelect(userId) {
    console.log("Selected:", userId);
  }

  return users.map((user) => (
    <UserCard
      key={user.id}
      user={user}
      onSelect={handleSelect}
    />
  ));
}
```

### Avoid unnecessary prop drilling

Passing a value through many components that do not use it can make the component tree difficult to maintain.

Before introducing global state, consider: - Moving the state closer to the components that need it. - Passing a value through a small number of component levels. - Using component composition. - Using Context for genuinely shared values. - Using Redux Toolkit when the application needs a central client-state store.

### Avoid passing huge objects when a component needs only a few values

Prefer a focused interface:

```jsx
<UserAvatar
  name={user.name}
  imageUrl={user.avatarUrl}
/>
```

rather than passing a large object if the component only needs two fields. Passing an object is still appropriate when the component genuinely represents that object.

---

## 5\. State management fundamentals

React state is a snapshot for a particular render. Calling a setter requests another render; it does not change the value in the currently running function.

### 5.1 Keep state minimal

### Bad: store values that can be calculated

```jsx
const [price, setPrice] = useState(100);
const [quantity, setQuantity] = useState(2);
const [total, setTotal] = useState(200);
```

Now every price or quantity update must also keep `total` in sync.

### Good: derive the value

```jsx
const [price, setPrice] = useState(100);
const [quantity, setQuantity] = useState(2);

const total = price * quantity;
```

### 5.2 Never mutate state directly

### Bad

```jsx
user.name = "Asha";
setUser(user);

items.push(newItem);
setItems(items);
```

React may not detect a changed reference, and mutation can cause difficult-to-debug bugs.

### Good

```jsx
setUser((previousUser) => ({
  ...previousUser,
  name: "Asha",
}));

setItems((previousItems) => [
  ...previousItems,
  newItem,
]);
```

For an array item update:

```jsx
setItems((previousItems) =>
  previousItems.map((item) =>
    item.id === updatedItem.id ? updatedItem : item
  )
);
```

For deletion:

```jsx
setItems((previousItems) =>
  previousItems.filter((item) => item.id !== itemId)
);
```

### 5.3 Do not store values in state if they are not part of the UI

Use a normal variable for a value that does not need to trigger a render. Use `useRef` for mutable values that must persist between renders without triggering a render.

---

## 6\. useState best practices

Use `useState` for simple component state such as input values, open/closed flags, selected tabs, and small independent values.

### 6.1 Basic usage

```jsx
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
```

### 6.2 Use functional updates when the next value depends on the previous value

### Risky when multiple updates use the same render snapshot

```jsx
function incrementThreeTimes() {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
}
```

Each call reads the same `count` snapshot.

### Good

```jsx
function incrementThreeTimes() {
  setCount((previous) => previous + 1);
  setCount((previous) => previous + 1);
  setCount((previous) => previous + 1);
}
```

### 6.3 Lazy initialization for expensive initial calculations

```jsx
const [settings, setSettings] = useState(() => {
  return loadInitialSettings();
});
```

The initializer is used to calculate the initial state. Do not pass `loadInitialSettings()` if you do not want it evaluated on every render.

### 6.4 Group related state when it makes updates clearer

A few related fields can be stored in one object:

```jsx
const [form, setForm] = useState({
  name: "",
  email: "",
});

function updateField(field, value) {
  setForm((previous) => ({
    ...previous,
    [field]: value,
  }));
}
```

Do not combine unrelated values into one giant state object just to reduce the number of Hooks.

---

## 7\. useEffect best practices

`useEffect` is for synchronizing a component with something outside React, such as a network connection, browser event, timer, or third-party widget.

It is not a general-purpose place to calculate values or respond to every user action.

### 7.1 Do not use an Effect for derived values

### Bad

```jsx
const [firstName, setFirstName] = useState("Maya");
const [lastName, setLastName] = useState("Rao");
const [fullName, setFullName] = useState("");

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
```

### Good

```jsx
const fullName = `${firstName} ${lastName}`;
```

### 7.2 Do not use an Effect for a user event

### Bad

```jsx
const [shouldSave, setShouldSave] = useState(false);

useEffect(() => {
  if (shouldSave) {
    saveUser();
  }
}, [shouldSave]);
```

### Good

```jsx
function handleSave() {
  saveUser();
}
```

The save is caused by a button click, so handle it in the event handler.

### 7.3 Correct dependency handling

Include every reactive value used inside the Effect. Do not silence dependency warnings just to make an Effect run less often. Restructure the code if the dependencies are causing an unwanted loop.

```jsx
useEffect(() => {
  document.title = `Users (${users.length})`;
}, [users.length]);
```

### 7.4 Clean up subscriptions and browser events

```jsx
useEffect(() => {
  function handleResize() {
    console.log(window.innerWidth);
  }

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);
```

### 7.5 Cancel or ignore outdated requests

```jsx
useEffect(() => {
  const controller = new AbortController();

  async function loadUser() {
    try {
      const response = await fetch(`/api/users/${userId}`, {
        signal: controller.signal,
      });

      if (!response.ok) {
        throw new Error("Could not load user");
      }

      const data = await response.json();
      setUser(data);
    } catch (error) {
      if (error.name !== "AbortError") {
        setError(error.message);
      }
    }
  }

  loadUser();

  return () => controller.abort();
}, [userId]);
```

This prevents an obsolete request from updating the component after the `userId` changes or the component unmounts.

### 7.6 Avoid Effects that update their own dependencies without a clear reason

An Effect that updates a value it depends on can create an infinite render loop. Check whether the update belongs in an event handler or can be calculated during render.

---

## 8\. Other important Hooks

### 8.1 useRef

Use `useRef` for a DOM reference or a mutable value that should persist across renders but should not itself trigger a render.

DOM example:

```jsx
import { useRef } from "react";

function SearchBox() {
  const inputRef = useRef(null);

  function focusInput() {
    inputRef.current?.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}
```

Mutable value example:

```jsx
const requestCount = useRef(0);
requestCount.current += 1;
```

Do not use a ref for data that must immediately appear in the UI. Use state for that.

### 8.2 useMemo

Use `useMemo` to cache the result of a costly calculation between renders when its dependencies have not changed.

```jsx
const filteredUsers = useMemo(() => {
  return users.filter((user) =>
    user.name.toLowerCase().includes(search.toLowerCase())
  );
}, [users, search]);
```

For a small array, calculate it directly:

```jsx
const filteredUsers = users.filter((user) =>
  user.name.toLowerCase().includes(search.toLowerCase())
);
```

Do not use `useMemo` by default for every variable.

### 8.3 useCallback

Use `useCallback` when a stable function reference is useful, for example when passing a callback to a memoized child or using it as a dependency of another Hook.

```jsx
const handleSelect = useCallback((id) => {
  setSelectedId(id);
}, []);
```

Do not wrap every event handler in `useCallback`. It adds complexity and does not automatically make the application faster.

### 8.4 React.memo

`React.memo` can skip a child render when its props have not changed.

```jsx
const UserCard = memo(function UserCard({ user, onSelect }) {
  return (
    <button onClick={() => onSelect(user.id)}>
      {user.name}
    </button>
  );
});
```

It is most useful when the component renders often, rendering is expensive, and props are stable. It is not a substitute for good state placement.

### 8.5 useId

Use `useId` to create stable IDs for accessibility relationships, such as connecting a label and input. Do not use it for list keys.

```jsx
const inputId = useId();

return (
  <>
    <label htmlFor={inputId}>Email</label>
    <input id={inputId} type="email" />
  </>
);
```

### 8.6 useLayoutEffect

Use `useLayoutEffect` only when you must measure or adjust layout before the browser paints, such as measuring an element to position a tooltip. Prefer `useEffect` for normal synchronization.

### 8.7 Rules of Hooks

- Call Hooks at the top level of a function component or custom Hook.
- Do not call Hooks inside conditions, loops, callbacks, or regular utility functions.
- Call Hooks in the same order on every render.
- Use the `use` prefix for custom Hooks.
- Follow the React Hooks ESLint rules.

### Bad

```jsx
if (isLoggedIn) {
  const [user, setUser] = useState(null);
}
```

### Good

```jsx
const [user, setUser] = useState(null);

if (!isLoggedIn) {
  return <Login />;
}
```

---

## 9\. Custom Hooks

Create a custom Hook when logic needs to be reused or when extracting it makes a component easier to read. A custom Hook shares logic, not one shared state instance: each call has its own state unless it uses a shared store or Context.

### Example: useWindowWidth

```jsx
import { useEffect, useState } from "react";

export function useWindowWidth() {
  const [width, setWidth] = useState(() => window.innerWidth);

  useEffect(() => {
    function handleResize() {
      setWidth(window.innerWidth);
    }

    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return width;
}
```

Usage:

```jsx
function ResponsiveMessage() {
  const width = useWindowWidth();

  return <p>Window width: {width}px</p>;
}
```

For server-rendered applications, account for the fact that `window` is not available during server rendering. Initialize safely or use an appropriate client-only approach.

### Bad: custom Hook that only renames a built-in Hook

```jsx
function useMyState(initialValue) {
  return useState(initialValue);
}
```

This adds no meaningful abstraction. Create custom Hooks to encapsulate useful behaviour, not merely to wrap one line.

---

## 10\. Context API and useContext

Context lets a parent provide a value to many descendants without manually passing it through every intermediate component.

Good use cases: - Current authenticated user and authentication actions. - Theme. - Locale or language. - A small, stable application-wide setting.

Context is not automatically a replacement for Redux or a server-state library.

### 10.1 Create a Context

```jsx
// context/ThemeContext.jsx
import { createContext, useContext, useState } from "react";

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  function toggleTheme() {
    setTheme((current) =>
      current === "light" ? "dark" : "light"
    );
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);

  if (context === null) {
    throw new Error("useTheme must be used inside ThemeProvider");
  }

  return context;
}
```

### 10.2 Wrap the application

```jsx
// main.jsx
import { createRoot } from "react-dom/client";
import App from "./App";
import { ThemeProvider } from "./context/ThemeContext";

createRoot(document.getElementById("root")).render(
  <ThemeProvider>
    <App />
  </ThemeProvider>
);
```

### 10.3 Consume Context

```jsx
import { useTheme } from "../context/ThemeContext";

function ThemeButton() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button onClick={toggleTheme}>
      Current theme: {theme}
    </button>
  );
}
```

### 10.4 Bad Context usage

Do not put every changing value in one giant Context:

```jsx
<AppContext.Provider
  value={{
    user,
    theme,
    cart,
    filters,
    modal,
    notifications,
    formValues,
  }}
>
  {children}
</AppContext.Provider>
```

This makes the provider difficult to maintain. Consumers can re-render when the provider value changes, even if they only need one part of it.

### Better

- Split Contexts by responsibility, such as `AuthContext` and `ThemeContext`.
- Keep frequently changing local state inside the feature that owns it.
- Memoize provider values only when it helps and dependencies are correct.
- Consider Redux Toolkit for complex shared client state.

### 10.5 When should you use Context?

Use Context when: - Many descendants need the same value. - The value is conceptually application-wide or subtree-wide. - Prop drilling is making the component tree harder to maintain. - The update frequency and complexity are manageable.

Avoid Context when: - Only one or two nearby components need the value. - The value changes very frequently and causes broad re-rendering. - You need advanced store features, middleware, DevTools workflows, or complex update patterns.

---

## 11\. useReducer

`useReducer` is a Hook for managing state through actions. It is useful when state transitions are more involved than a few independent setter calls.

### 11.1 Basic structure

A reducer receives the current state and an action, then returns the next state.

```jsx
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };

    case "decrement":
      return { count: state.count - 1 };

    case "reset":
      return { count: 0 };

    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}
```

Use it in a component:

```jsx
import { useReducer } from "react";

const initialState = { count: 0 };

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <h2>{state.count}</h2>

      <button onClick={() => dispatch({ type: "increment" })}>
        +
      </button>

      <button onClick={() => dispatch({ type: "decrement" })}>
        -
      </button>

      <button onClick={() => dispatch({ type: "reset" })}>
        Reset
      </button>
    </>
  );
}
```

### 11.2 Practical example: form state

When several related fields and transitions are involved, a reducer can make the update rules explicit.

```jsx
const initialState = {
  name: "",
  email: "",
  errors: {},
  status: "idle",
};

function formReducer(state, action) {
  switch (action.type) {
    case "fieldChanged":
      return {
        ...state,
        [action.field]: action.value,
        errors: {
          ...state.errors,
          [action.field]: "",
        },
      };

    case "validationFailed":
      return {
        ...state,
        errors: action.errors,
        status: "idle",
      };

    case "submitting":
      return { ...state, status: "loading" };

    case "submitted":
      return { ...state, status: "success" };

    case "failed":
      return { ...state, status: "error" };

    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}
```

```jsx
function RegistrationForm() {
  const [state, dispatch] = useReducer(formReducer, initialState);

  function handleChange(event) {
    const { name, value } = event.target;

    dispatch({
      type: "fieldChanged",
      field: name,
      value,
    });
  }

  return (
    <form>
      <input
        name="name"
        value={state.name}
        onChange={handleChange}
      />

      <input
        name="email"
        value={state.email}
        onChange={handleChange}
      />

      <p>Status: {state.status}</p>
    </form>
  );
}
```

This is only the state-management part of a form; add validation, submission, and accessible labels for a production form.

### 11.3 useReducer best practices

- Keep reducers pure: no API calls, timers, random values, or DOM operations inside them.
- Do not mutate the existing state.
- Use descriptive action names, such as `itemAdded` or `requestFailed`.
- Keep initial state in a clearly named constant.
- Keep reducer logic outside the component when it is substantial.
- Do not use a reducer for trivial state that is clearer with `useState`.

### 11.4 When should you use useReducer?

Choose `useReducer` when: - Several state values change together. - Many event handlers update the same state object. - The next state depends on the current state and the action. - You want transitions to be centralized and easy to test. - A form, wizard, cart, or local workflow has multiple states and transitions.

Use `useState` when: - State is simple and independent. - A boolean, input value, selected tab, or small value is all you need. - A reducer would add more code than clarity.

---

## 12\. Choosing useState, useReducer, Context, or Redux Toolkit

These tools solve related but different problems. They are not mutually exclusive: for example, Context can provide a reducer's state and dispatch to descendants.

---

Tool Best for Example

---

`useState` Simple local state Input value, modal open

`useReducer` Related state with Multi-step form, local multiple transitions cart logic

Context + `useState` Shared, relatively Theme, current user simple state

Context + `useReducer` Shared state with Small workflow shared structured transitions by many descendants

Redux Toolkit Complex shared client Large app with shared state across features entities and workflows

## TanStack Query / RTK Server state and API Fetching, caching, Query caching refetching API data

### Decision guide

**Use** `useState` if one component owns a simple value.

**Use** `useReducer` if one feature owns a group of related values and has several meaningful transitions.

**Use Context** if many descendants need the same value and passing props is becoming awkward.

**Use Context +** `useReducer` if shared state has several related transitions but the app does not need a full Redux store.

**Use Redux Toolkit** if many features need coordinated shared client state, predictable action-based updates, middleware, strong Redux DevTools support, or a standard store architecture.

**Use a server-state tool** when the hard problem is fetching, caching, synchronizing, and invalidating remote data. Redux is not automatically the best place for all API response data.

### Example choices

---

Requirement Good starting point

---

Open/close one dialog `useState`

Filter a table on one page `useState`

Multi-step checkout state inside `useReducer` one feature

Theme across the app Context

Authenticated user shared across Context or Redux Toolkit, depending many components on complexity

Shopping cart shared across many Redux Toolkit may be appropriate routes with complex actions

Cache product API results TanStack Query or RTK Query

## Server-side form validation API + form state/validation library

Do not choose Redux just because the application has many components. Choose it when the state-sharing and update requirements justify a centralized store.

---

## 13\. Redux Toolkit

Redux Toolkit (RTK) is the recommended way to write Redux logic. It reduces boilerplate and provides a standard approach to reducers, actions, and store setup.

Use it for complex shared **client state**. For remote API data, consider RTK Query (included with Redux Toolkit) or another server-state library.

### 13.1 Install

```bash
npm install @reduxjs/toolkit react-redux
```

### 13.2 Create a slice

```jsx
// features/cart/cartSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  items: [],
};

const cartSlice = createSlice({
  name: "cart",
  initialState,
  reducers: {
    itemAdded(state, action) {
      state.items.push(action.payload);
    },

    itemRemoved(state, action) {
      state.items = state.items.filter(
        (item) => item.id !== action.payload
      );
    },

    cartCleared(state) {
      state.items = [];
    },
  },
});

export const { itemAdded, itemRemoved, cartCleared } =
  cartSlice.actions;

export default cartSlice.reducer;
```

RTK uses Immer internally, so this reducer syntax is safe inside `createSlice`. Do not copy this mutation-style syntax into ordinary React state updates or plain reducers.

### 13.3 Configure the store

```jsx
// app/store.js
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "../features/cart/cartSlice";

export const store = configureStore({
  reducer: {
    cart: cartReducer,
  },
});
```

### 13.4 Add the Provider

```jsx
// main.jsx
import { Provider } from "react-redux";
import { store } from "./app/store";

createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);
```

### 13.5 Read state and dispatch actions

```jsx
import { useDispatch, useSelector } from "react-redux";
import { itemAdded } from "./cartSlice";

function Cart() {
  const items = useSelector((state) => state.cart.items);
  const dispatch = useDispatch();

  return (
    <>
      <p>Items: {items.length}</p>
      <button
        onClick={() =>
          dispatch(itemAdded({ id: 1, name: "Keyboard" }))
        }
      >
        Add item
      </button>
    </>
  );
}
```

### 13.6 Redux Toolkit best practices

- Create slices around features or meaningful state domains.
- Keep reducers synchronous and pure in intent; use RTK's async patterns for asynchronous workflows.
- Use selectors to read only the state a component needs.
- Avoid copying the same data into multiple slices.
- Do not put temporary input state in Redux without a good reason.
- Keep non-serializable values such as DOM nodes, functions, and class instances out of the store.
- Use Redux DevTools to inspect actions and state changes.
- Consider RTK Query for API fetching and caching rather than manually building every request lifecycle.

### 13.7 When Redux Toolkit is unnecessary

For a small app with a few local states, adding Redux creates extra setup without much benefit. Start with component state and Context where appropriate, then introduce Redux Toolkit when the actual requirements justify it.

---

## 14\. API calls and data fetching

Keep network communication out of large UI components. Put reusable request configuration in an API client and feature-specific endpoints in service modules.

### 14.1 Example folder structure

```text
src/
├── services/
│   └── apiClient.js
└── features/
    └── users/
        ├── services/
        │   └── userService.js
        ├── hooks/
        │   └── useUsers.js
        └── UsersPage.jsx
```

### 14.2 Centralize the API base URL

For a Vite project:

```env
VITE_API_BASE_URL=https://api.example.com
```

```jsx
// services/apiClient.js
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;

export async function apiRequest(path, options = {}) {
  const response = await fetch(`${API_BASE_URL}${path}`, {
    ...options,
    headers: {
      "Content-Type": "application/json",
      ...options.headers,
    },
  });

  if (!response.ok) {
    const message = `Request failed: ${response.status}`;
    throw new Error(message);
  }

  if (response.status === 204) {
    return null;
  }

  return response.json();
}
```

This is a simple starting point. Production clients may also need authentication handling, structured error parsing, request IDs, timeouts, and special handling for file uploads. Do not set `Content-Type: application/json` for `FormData`; let the browser set its multipart boundary.

### 14.3 Create feature-specific API functions

```jsx
// features/users/services/userService.js
import { apiRequest } from "../../../services/apiClient";

export function getUsers({ signal } = {}) {
  return apiRequest("/users", { signal });
}

export function getUserById(id, { signal } = {}) {
  return apiRequest(`/users/${id}`, { signal });
}

export function createUser(payload) {
  return apiRequest("/users", {
    method: "POST",
    body: JSON.stringify(payload),
  });
}
```

### 14.4 Bad: API calls scattered through UI components

```jsx
function UsersPage() {
  async function handleClick() {
    const response = await fetch("https://api.example.com/users");
    const data = await response.json();
    // More request and transformation logic here
  }

  return <button onClick={handleClick}>Load</button>;
}
```

This becomes difficult to reuse, test, and maintain when many components repeat the same URL, headers, and error handling.

### 14.5 Good: service layer plus a Hook

```jsx
// features/users/hooks/useUsers.js
import { useEffect, useState } from "react";
import { getUsers } from "../services/userService";

export function useUsers() {
  const [users, setUsers] = useState([]);
  const [status, setStatus] = useState("loading");
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function loadUsers() {
      setStatus("loading");
      setError(null);

      try {
        const data = await getUsers({
          signal: controller.signal,
        });

        setUsers(data);
        setStatus("success");
      } catch (err) {
        if (err.name === "AbortError") return;

        setError(err);
        setStatus("error");
      }
    }

    loadUsers();

    return () => controller.abort();
  }, []);

  return { users, status, error };
}
```

Component:

```jsx
function UsersPage() {
  const { users, status, error } = useUsers();

  if (status === "loading") return <p>Loading users...</p>;
  if (status === "error") return <p>{error.message}</p>;
  if (users.length === 0) return <p>No users found.</p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
```

This example is suitable for a straightforward request. If the application needs caching, request deduplication, background refetching, pagination, or cache invalidation, use a data-fetching library instead of building all of that yourself.

### 14.6 When to use TanStack Query or RTK Query

Consider a server-state library when you need: - Caching and request deduplication. - Background refetching and stale-data handling. - Cache invalidation after mutations. - Pagination or infinite queries. - Consistent loading and error states. - Avoiding repeated manual request logic.

Useful resources: - TanStack Query: <https://tanstack.com/query/latest> - RTK Query: <https://redux-toolkit.js.org/rtk-query/overview>

### 14.7 API call rules

- Check `response.ok` when using `fetch`; `fetch` does not reject just because the server returns 404 or 500.
- Handle network errors and server errors.
- Cancel obsolete requests when appropriate.
- Do not expose secret API keys in frontend environment variables.
- Do not assume the response always contains valid data.
- Avoid triggering the same request from multiple unrelated components when a shared query cache is more suitable.
- Keep request and response types/contracts clear.

---

## 15\. Loading, error, empty, and success states

Every API-driven screen should deliberately handle each state.

State What to show

---

Loading Spinner, skeleton, or progress message Success with data Actual content Success with no data Helpful empty state Error Clear message and retry option where useful

### Bad

```jsx
return <div>{users.map((user) => user.name)}</div>;
```

This may fail if `users` is undefined and gives no feedback while the request is running or fails.

### Good

```jsx
if (status === "loading") {
  return <p>Loading...</p>;
}

if (status === "error") {
  return (
    <div role="alert">
      <p>Unable to load users.</p>
      <button onClick={refetch}>Try again</button>
    </div>
  );
}

if (users.length === 0) {
  return <p>No users are available yet.</p>;
}

return (
  <ul>
    {users.map((user) => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);
```

Do not show raw stack traces or internal server details to end users. Log useful diagnostic details through an approved logging system.

---

## 16\. Forms and validation

### 16.1 Use controlled inputs when React state should own the value

```jsx
function NameForm() {
  const [name, setName] = useState("");

  function handleSubmit(event) {
    event.preventDefault();
    console.log(name);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="name">Name</label>
      <input
        id="name"
        value={name}
        onChange={(event) => setName(event.target.value)}
      />
      <button type="submit">Submit</button>
    </form>
  );
}
```

### 16.2 Avoid unnecessary state for every derived form value

Calculate simple values from the current form state rather than maintaining duplicate values.

### 16.3 Use form libraries for complex forms

Useful options: - React Hook Form: <https://react-hook-form.com/> - Zod: <https://zod.dev/> - Yup: <https://github.com/jquense/yup>

Use a schema validation library when forms have multiple fields, conditional rules, nested data, or shared validation requirements.

### 16.4 Validate on both client and server

Client-side validation improves the user experience. Backend validation is still required because client code can be bypassed.

### 16.5 Handle submission state

Prevent accidental duplicate submissions, show progress, display field-level errors where possible, and provide a clear success result.

---

## 17\. Rendering and performance

### 17.1 Use stable keys

### Bad

```jsx
users.map((user, index) => (
  <UserCard key={index} user={user} />
))
```

### Good

```jsx
users.map((user) => (
  <UserCard key={user.id} user={user} />
))
```

Use a stable unique ID from the data. Index keys are only reasonable for static lists that never reorder, insert, or delete items.

### 17.2 Avoid unnecessary re-renders

- Keep state close to the components that use it.
- Avoid changing Context values unnecessarily.
- Avoid recreating large derived data without need.
- Use memoization when profiling shows it helps.
- Split large components when it improves ownership and rendering boundaries.

### 17.3 Lazy-load route-level components

```jsx
import { lazy, Suspense } from "react";

const ReportsPage = lazy(() => import("./pages/ReportsPage"));

function App() {
  return (
    <Suspense fallback={<p>Loading page...</p>}>
      <ReportsPage />
    </Suspense>
  );
}
```

Use a router's route-level lazy-loading support where available.

### 17.4 Virtualize large lists

For thousands of rows, rendering every row can be expensive. Use pagination or a virtualization library such as: - TanStack Virtual: <https://tanstack.com/virtual/latest>

### 17.5 Optimize assets

- Compress images.
- Prefer modern formats such as WebP or AVIF where suitable.
- Lazy-load offscreen images.
- Set image width and height to reduce layout shifts.
- Avoid shipping large assets that are not needed on the current page.

### 17.6 Measure before optimizing

Use React Developer Tools Profiler and browser performance tools to identify actual bottlenecks. Avoid adding `memo`, `useMemo`, and `useCallback` everywhere.

---

## 18\. Routing and code splitting

For multi-page applications, use a routing library such as React Router.

Good routing practices: - Keep route definitions in a clear location. - Use layouts for shared navigation and page structure. - Handle unknown routes with a not-found page. - Protect private routes based on authentication and authorization checks. - Lazy-load large route components where appropriate. - Do not treat hiding a frontend route as backend authorization; protect API endpoints on the server too.

React Router documentation: <https://reactrouter.com/>

---

## 19\. Error boundaries and error handling

### 19.1 Use error boundaries for render errors

Error boundaries catch errors thrown while rendering descendant components and show a fallback UI. They do not automatically catch errors in event handlers or asynchronous callbacks.

A class-based boundary remains a common implementation:

```jsx
import { Component } from "react";

class ErrorBoundary extends Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Send diagnostic details to an approved logging service.
    console.error(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h2>Something went wrong.</h2>;
    }

    return this.props.children;
  }
}
```

Usage:

```jsx
<ErrorBoundary>
  <Dashboard />
</ErrorBoundary>
```

Place boundaries around meaningful parts of the application so one broken area does not necessarily take down the entire UI.

### 19.2 Handle event and API errors explicitly

```jsx
async function handleSave() {
  try {
    setStatus("loading");
    await saveUser(formData);
    setStatus("success");
  } catch (error) {
    setStatus("error");
    setErrorMessage("Unable to save your changes. Please try again.");
  }
}
```

Do not assume an error boundary will catch this asynchronous failure. Handle it in the request or event flow.

### 19.3 Do not swallow errors

Bad:

```jsx
try {
  await saveUser();
} catch (error) {
  // Nothing happens
}
```

Good:

```jsx
try {
  await saveUser();
} catch (error) {
  setErrorMessage("Save failed. Please try again.");
  reportError(error);
}
```

Use an appropriate error-reporting mechanism for production; avoid logging sensitive data.

---

## 20\. Security and accessibility

### 20.1 Security

- Never put private API secrets in frontend code or frontend environment variables.
- Validate and authorize sensitive operations on the backend.
- Treat API responses and user input as untrusted.
- Avoid `dangerouslySetInnerHTML` unless HTML rendering is genuinely required and the content is sanitized with a trusted sanitizer.
- Use secure authentication and session practices appropriate to the application.
- Avoid putting sensitive information in URLs, logs, or browser storage.
- Keep dependencies updated and review security advisories.

### Bad: rendering untrusted HTML

```jsx
<div dangerouslySetInnerHTML={{ __html: userProvidedHtml }} />
```

### Better: render text normally

```jsx
<p>{userProvidedText}</p>
```

If rich HTML is a requirement, sanitize it using a trusted library and a carefully defined policy.

### 20.2 Accessibility

Use semantic HTML:

```jsx
<button type="button" onClick={handleSave}>
  Save
</button>
```

Avoid using a clickable `div` when a button is appropriate.

Also: - Associate labels with form fields. - Provide useful alternative text for meaningful images. - Ensure keyboard navigation works. - Use visible focus styles. - Use `aria-live` or suitable status semantics for important dynamic feedback. - Give dialogs correct focus management and keyboard behaviour. - Do not rely on colour alone to communicate status.

---

## 21\. Testing

Test behaviour that matters to users, not just implementation details.

### 21.1 Useful tools

- Vitest: <https://vitest.dev/>
- React Testing Library: <https://testing-library.com/docs/react-testing-library/intro/>
- Playwright: <https://playwright.dev/>
- Cypress: <https://www.cypress.io/>

### 21.2 Component test example

```jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Counter } from "./Counter";

test("increments the counter when clicked", async () => {
  const user = userEvent.setup();

  render(<Counter />);

  await user.click(
    screen.getByRole("button", { name: /count/i })
  );

  expect(screen.getByText("Count: 1")).toBeInTheDocument();
});
```

### 21.3 What to test

- Important user interactions.
- Form validation and submission.
- Loading, error, empty, and success states.
- Conditional rendering.
- Reducer transitions and utility functions.
- Critical end-to-end flows such as login, checkout, and record creation.

Avoid tests that depend heavily on internal component state or exact implementation details. Prefer queries by role and accessible name.

---

## 22\. Code quality and maintainability

### 22.1 Use ESLint

ESLint catches potential bugs and inconsistent patterns. For React projects, configure the React and Hooks rules and follow the project's framework recommendations.

### 22.2 Use Prettier

Prettier formats code consistently. Agree on formatting rules and run formatting automatically where possible.

### 22.3 Use clear names

Bad:

```jsx
const d = users.filter((x) => x.a);
```

Good:

```jsx
const activeUsers = users.filter((user) => user.isActive);
```

### 22.4 Avoid magic values

Bad:

```jsx
if (user.role === 3) {
  // ...
}
```

Good:

```jsx
const USER_ROLES = {
  ADMIN: "admin",
  MEMBER: "member",
};

if (user.role === USER_ROLES.ADMIN) {
  // ...
}
```

Use constants when they make meaning clearer; do not create a constants file for every literal.

### 22.5 Avoid duplicate logic

If multiple features repeat the same meaningful business rule, extract it into a utility, custom Hook, or service at the appropriate level.

### 22.6 Keep dependencies intentional

Before installing a package: - Check whether the platform or React already provides the feature. - Check maintenance activity and compatibility. - Check package size and security. - Avoid adding a library for a trivial one-line task.

---

## 23\. Example feature structure

Here is a practical structure for a Users feature in a medium-sized application:

```text
src/
├── app/
│   ├── App.jsx
│   ├── routes.jsx
│   └── store.js
│
├── features/
│   └── users/
│       ├── components/
│       │   ├── UserCard.jsx
│       │   ├── UserFilters.jsx
│       │   └── UserForm.jsx
│       │
│       ├── hooks/
│       │   └── useUsers.js
│       │
│       ├── services/
│       │   └── userService.js
│       │
│       ├── utils/
│       │   └── userHelpers.js
│       │
│       ├── UsersPage.jsx
│       └── UserDetailsPage.jsx
│
├── shared/
│   ├── components/
│   │   ├── Button.jsx
│   │   ├── Modal.jsx
│   │   └── Spinner.jsx
│   ├── hooks/
│   └── utils/
│
├── services/
│   └── apiClient.js
│
├── assets/
├── styles/
└── main.jsx
```

### Responsibility of each area

Folder Responsibility

---

`app/` Application setup, routes, providers, store `features/` Feature-specific pages, components, Hooks, services `shared/components/` Reusable UI components `shared/hooks/` Reusable cross-feature Hooks `shared/utils/` General helper functions `services/` Shared API client and network configuration `assets/` Images, icons, fonts `styles/` Global styles and design tokens

Do not force every feature to have every folder. Add files and folders as the feature needs them.

---

## 24\. Code review checklist

Use this checklist before raising a pull request or completing a feature.

### Components and structure

- [ ] Components have clear responsibilities.
- [ ] Large components are split where it improves clarity.
- [ ] Component and file names follow the project convention.
- [ ] Feature-specific code is grouped together.
- [ ] Reusable components are genuinely reusable.

### State and Hooks

- [ ] State is kept as local as possible.
- [ ] Derived values are calculated instead of duplicated in state.
- [ ] State arrays and objects are updated immutably.
- [ ] Functional updates are used when the next value depends on the previous value.
- [ ] Hooks are called only at the top level.
- [ ] `useEffect` is used for external synchronization, not routine calculations.
- [ ] Effects have correct dependencies and cleanup where required.
- [ ] Memoization is used only when useful.

### Context and state management

- [ ] Context is used only for values needed by multiple descendants.
- [ ] Context providers are not overloaded with unrelated state.
- [ ] `useReducer` is used when transitions are complex enough to justify it.
- [ ] Redux Toolkit is used only when shared client-state needs justify a store.
- [ ] Server data is handled with an appropriate fetching and caching strategy.

### API and error handling

- [ ] API calls are separated into service or query modules.
- [ ] HTTP error responses are checked.
- [ ] Loading, error, empty, and success states are handled.
- [ ] Obsolete requests are cancelled or safely ignored where needed.
- [ ] Errors are not silently swallowed.
- [ ] No private API secrets are exposed in frontend code.

### UI, performance, and quality

- [ ] Lists use stable keys.
- [ ] Forms have labels and validation.
- [ ] Interactive elements are keyboard accessible.
- [ ] Large lists use pagination or virtualization when needed.
- [ ] Images and route bundles are optimized where appropriate.
- [ ] Important behaviour has tests.
- [ ] ESLint and formatting checks pass.
- [ ] No unnecessary dependencies or dead code are introduced.

---

## 25\. Useful official resources

Use the official documentation as the source of truth, especially because React APIs and recommended patterns evolve.

### React fundamentals and Hooks

- React Learn: <https://react.dev/learn>
- Describing the UI: <https://react.dev/learn/describing-the-ui>
- Adding Interactivity: <https://react.dev/learn/adding-interactivity>
- Managing State: <https://react.dev/learn/managing-state>
- Escape Hatches and Effects: <https://react.dev/learn/escape-hatches>
- Built-in Hooks: <https://react.dev/reference/react/hooks>
- Rules of Hooks: <https://react.dev/reference/rules/rules-of-hooks>
- You Might Not Need an Effect: <https://react.dev/learn/you-might-not-need-an-effect>

### State management

- Context: <https://react.dev/learn/passing-data-deeply-with-context>
- Reducer: <https://react.dev/learn/extracting-state-logic-into-a-reducer>
- Scaling with Reducer and Context: <https://react.dev/learn/scaling-up-with-reducer-and-context>
- Redux Toolkit: <https://redux-toolkit.js.org/>
- RTK Query: <https://redux-toolkit.js.org/rtk-query/overview>
- TanStack Query: <https://tanstack.com/query/latest>

### Quality and testing

- React Developer Tools: <https://react.dev/learn/react-developer-tools>
- React Testing Library: <https://testing-library.com/docs/react-testing-library/intro/>
- Vitest: <https://vitest.dev/>
- Playwright: <https://playwright.dev/>
- Web Accessibility: <https://web.dev/learn/accessibility>

---

## Final guidance

Do not try to introduce every React tool into every project.

A sensible starting point for most applications is:

1. Function components with clear responsibilities.
2. `useState` for simple local state.
3. `useEffect` only for synchronization with external systems.
4. Custom Hooks for reusable behaviour.
5. Context for a small number of shared values.
6. `useReducer` for complex local state transitions.
7. Redux Toolkit when shared client-state complexity justifies it.
8. TanStack Query or RTK Query for server-state fetching and caching.
9. A feature-based folder structure as the application grows.
10. Tests, accessibility, and error handling as part of normal development---not as last-minute additions.
