---
name: react-component-patterns
description: >-
  React component and hook design: composition over prop explosion, avoiding
  unnecessary effects, key-based state reset, custom hook extraction, and
  state colocation. Use when building or refactoring components.
argument-hint: "[component]"
license: MIT
metadata:
  author: John Felix Lim
  version: "1.0.0"
---
# Component Patterns

Apply when writing or reviewing components. Before adding an effect, ask what external system it synchronizes with — if the answer is 'none', it should be render-time logic or a key instead.

## Rules

1. **Most effects are a mistake** — Syncing state to other state, transforming props into state, or computing derived values in an effect all cause an extra render and a window where the UI is wrong. Effects are for synchronizing with systems outside React — nothing else.
2. **Compose with children instead of growing a prop list** — A component with fourteen boolean props is a component that does fourteen things badly. Accept `children` or slots and let the caller assemble what they need.
3. **A key change is a remount** — When state must reset because the subject changed, `key={id}` is the entire solution. It replaces the effect-that-resets-state pattern, and it is correct in cases the effect version silently isn't.
4. **Custom hooks own behaviour, components own markup** — When a component's logic outgrows its JSX, the logic wants to be a hook. It becomes testable on its own and reusable in the next screen that needs the same behaviour.
5. **Lift state only as far as it actually needs to go** — State hoisted to a common ancestor 'just in case' re-renders that whole subtree forever. Push it down until something genuinely shared forces it up.

## Patterns

### Derived values

**Avoid** — An extra render, plus a frame where the two arrays disagree.

```tsx
const [items, setItems] = useState<Item[]>([]);
const [visible, setVisible] = useState<Item[]>([]);

useEffect(() => {
  setVisible(items.filter((i) => !i.archived));
}, [items]);
// Renders once with stale `visible`, then again after the effect.
```

**Prefer** — One render, always consistent. Add `useMemo` only if profiling says the filter is expensive.

```tsx
const [items, setItems] = useState<Item[]>([]);

// Computed during render. Never stale, never an extra pass.
const visible = items.filter((item) => !item.archived);
```

### Resetting state on prop change

**Avoid** — There is always one render where the new user is displayed with the old user's input.

```tsx
function ProfileForm({ userId }: Props) {
  const [draft, setDraft] = useState("");

  useEffect(() => {
    setDraft("");   // clear when switching user
  }, [userId]);

  // Renders once with the *previous* user's draft still showing.
}
```

**Prefer** — React discards the old instance entirely. There is no intermediate wrong state to leak.

```tsx
// Parent — a new key remounts the form, resetting all its state.
<ProfileForm key={userId} userId={userId} />

function ProfileForm({ userId }: Props) {
  const [draft, setDraft] = useState("");
  // No effect, and no window where the state is wrong.
}
```

### Composition over configuration

**Avoid** — Every new requirement adds a prop. The component accretes branches until nobody dares change it.

```tsx
<Card
  title="Revenue"
  subtitle="Q4"
  showIcon
  iconName="chart"
  showFooter
  footerText="Updated 2h ago"
  footerAlign="right"
  variant="elevated"
  hasBorder={false}
/>
```

**Prefer** — New layouts need no changes to Card. The caller composes exactly what it needs.

```tsx
<Card variant="elevated">
  <Card.Header icon={<ChartIcon />}>
    <Card.Title>Revenue</Card.Title>
    <Card.Subtitle>Q4</Card.Subtitle>
  </Card.Header>
  <Card.Footer align="right">Updated 2h ago</Card.Footer>
</Card>
```

## Review checklist

- [ ] No effect exists purely to compute state from other state or props.
- [ ] State resets on identity change use `key`, not an effect.
- [ ] Components take `children` or slots rather than long boolean prop lists.
- [ ] Every effect has a genuine external system and a correct cleanup.
- [ ] State lives at the lowest level that works.
- [ ] Reusable behaviour is extracted into named custom hooks.

---

From the John Felix Lim Frontend Engineering Playbook — https://github.com/JohnFelixLim