Senior Tips & Tricks

A grab-bag of small, high-leverage habits that separate tidy React from fragile React.

None of these are big enough for their own lesson, but together they are the texture of code written by someone who has shipped React for years. Steal freely.

Reset a component with a key

Changing a component's key makes React throw away the old instance and mount a fresh one. It is the cleanest way to reset internal state — e.g. <EditForm key={userId} /> gives each user a clean form with no manual clearing.

Prefer the functional updater

setCount(c => c + 1) beats setCount(count + 1) whenever the next value depends on the previous one. It is correct under batching, safe in effects and intervals, and lets you drop the value from dependency arrays.

Lazy initial state

Pass a function to useState when the initial value is expensive: useState(() => parse(bigBlob)). Passing the value directly runs the work on every render and throws it away.

Guard boolean rendering

Use items.length > 0 && ..., not items.length && ... — the latter renders a stray 0. For anything non-trivial, a ternary is clearer than chained &&.

Small, honest components

  • One responsibility per component; if it is hard to name, it is doing too much.
  • Destructure props in the signature so the component's inputs are visible at a glance.
  • Colocate a component with its hooks, tests and styles — features, not layers.

Effects: last resort, always cleaned up

Ask 'is this really synchronizing with something outside React?' before writing an effect. If yes, return a cleanup for every subscription, timer and listener — StrictMode double-invokes effects in dev precisely to expose the ones you forgot.

Let the platform help

Reach for semantic HTML (button, label, dialog), native form validation, and useId for ARIA wiring before hand-rolling. And lean on the ecosystem — TanStack Query for server state, a real store for global state, a virtualization lib for big lists — instead of rebuilding hard things badly.

Example

Example · javascript
import { useState } from 'react';

function UserEditor({ users }) {
  const [selectedId, setSelectedId] = useState(users[0]?.id);

  return (
    <div>
      <select
        value={selectedId}
        onChange={(e) => setSelectedId(Number(e.target.value))}
      >
        {users.map((u) => (
          <option key={u.id} value={u.id}>{u.name}</option>
        ))}
      </select>

      {/* key resets EditForm's internal state when the user changes -
          no manual clearing, no stale draft carried over. */}
      <EditForm key={selectedId} userId={selectedId} />
    </div>
  );
}

function EditForm({ userId }) {
  // Lazy initial state: run the expensive default only once.
  const [draft, setDraft] = useState(() => loadDraft(userId));
  return (
    <textarea value={draft} onChange={(e) => setDraft(e.target.value)} />
  );
}

function loadDraft(userId) {
  return `Draft for user ${userId}`;
}

When to use it

  • Changing a <ProfileForm key={userId}> key when switching between user profiles forces React to discard the previous form state and mount a clean instance without writing reset logic.
  • Replacing a boolean isLoading flag with a status enum ('idle' | 'loading' | 'success' | 'error') eliminates impossible states like isLoading=true and isError=true simultaneously.
  • Colocating a list component's sort and filter state in a single useReducer call makes the state machine explicit and testable rather than spread across five useState calls.

More examples

Reset component state with a key

Binding the key to userId tells React to treat each user's form as a completely different component instance, resetting all local state for free.

Example · js
function UserProfile({ userId }) {
  // When userId changes, React unmounts the old EditForm
  // and mounts a fresh one — no manual reset needed
  return <EditForm key={userId} userId={userId} />;
}

function EditForm({ userId }) {
  const [draft, setDraft] = React.useState('');
  // draft starts fresh for every new userId automatically
  return <textarea value={draft} onChange={(e) => setDraft(e.target.value)} />;
}

Replace booleans with status enum

A status enum makes impossible states unrepresentable; each transition is explicit and the UI can match on a single value instead of combinations of booleans.

Example · js
// Fragile: isLoading and isError can both be true
const [isLoading, setIsLoading] = React.useState(false);
const [isError,   setIsError]   = React.useState(false);

// Robust: only one status at a time
const [status, setStatus] = React.useState('idle');
// 'idle' | 'loading' | 'success' | 'error'

async function load() {
  setStatus('loading');
  try {
    await fetchData();
    setStatus('success');
  } catch {
    setStatus('error');
  }
}

Batch updates with useReducer

useReducer batches related state into one atomic update, preventing the multiple re-renders that would occur from three separate useState setter calls.

Example · js
const initialState = { query: '', page: 1, sort: 'asc' };

function reducer(state, action) {
  switch (action.type) {
    case 'search': return { ...state, query: action.query, page: 1 };
    case 'next':   return { ...state, page: state.page + 1 };
    case 'sort':   return { ...state, sort: action.sort, page: 1 };
    default:       return state;
  }
}

function SearchPage() {
  const [state, dispatch] = React.useReducer(reducer, initialState);
  // dispatch({ type: 'search', query: 'react' }) — single re-render
}

Discussion

  • Be the first to comment on this lesson.