Hooks

usePrevious

A hook that returns the last distinct value (Object.is) before the current one, via render-time state adjustment instead of a ref+effect.

Preview in your theme

Loading preview…

"use client"

import * as React from "react"

/**
 * Tracks the LAST DISTINCT value (compared with `Object.is`) — repeated
 * equal values do not advance it, so after a "flat" update `previous` still
 * holds the value from before the last real change (which is exactly what
 * direction indicators want). Returns `undefined` on the very
 * first render — there is no "previous" before the first one.
 *
 * Implementation note: this is deliberately NOT the classic
 * `useRef` + `useEffect` version (`ref.current = value` written inside an
 * effect, read back during render). That version only updates the ref after

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/use-previous.json

Prompt

Build a React + TypeScript "usePrevious" hook (no dependencies beyond React).

Contract
- `usePrevious<T>(value: T): T | undefined`.
- Returns `undefined` on the first render for a given hook instance.
- From the second render on, returns whatever `value` was during the
  moment before the last DISTINCT change — repeated equal values do not
  advance it (an equality-gated snapshot, not a strict render-history log).

Behavior
- Comparison is `Object.is(value, <currently tracked value>)` — reference
  equality, the same check React itself uses for bailing out of state
  updates. A new object/array with identical-looking contents counts as a
  change; mutating the same object in place and passing the same reference
  does not.
- Implemented as one piece of state, `{ current, previous }`, not a `useRef`
  + `useEffect` pair: when a render observes `value !== current` (via
  `Object.is`), it calls `setState` right there in the render body to shift
  `current` into `previous` before paint — React's documented "adjust state
  during render" pattern. This keeps the update inside the same render pass,
  so the result is deterministic under StrictMode's double-invoke and safe
  if React discards and retries a concurrent render. The ref+effect version
  instead writes `.current` from inside an effect, one commit after the
  render that reads it — the ordering across StrictMode/concurrent renders
  is not well-defined, and reading a ref's `.current` synchronously during
  render is a smell most hook lint setups (including this repo's) call out.
- No effect, no ref, no timer, and nothing to clean up on unmount.

Rendering & styling
- Renders nothing itself — it is a pure value hook. Consumers own all UI,
  e.g. compare the returned previous value against the current one to
  render an up/down arrow with `text-primary` / `text-destructive`, or check
  against `undefined` to special-case the first render.

Customization levers
- Swap `Object.is` for a custom equality function (e.g. shallow-equal for
  objects, or a rounding comparator for near-equal floats) if reference
  equality is too strict for a given value type.
- Derive a `direction: "up" | "down" | null` from the returned previous value,
  as the demo does, when a consumer only cares about the sign of the change
  rather than the raw previous value itself.

Concepts

  • Render-time state adjustment — the React-documented pattern of calling setState conditionally inside the render body (not inside useEffect) to derive one piece of state from another; React detects the change and re-renders immediately, in the same commit, instead of committing stale UI and correcting it a tick later.
  • Object.is comparison — the hook compares by reference, exactly like React's own state bailout check; a structurally-identical-but-new object registers as a change, and an in-place mutation of the same object does not.
  • Why not useRef + useEffect — that version writes ref.current = value after the render commits, so the render that reads ref.current is always racing the effect that last wrote it; under StrictMode's double-invoked effects or an interrupted/resumed concurrent render, which write "wins" is not guaranteed. It also means reading a ref's .current synchronously during render, which this repo's lint setup flags.
  • One render behind, not a history stack — the hook only ever holds the single immediately-preceding value; it cannot answer "what was the last different value three changes ago" (see Not when).
  • Derived direction, not stored direction — the demo's up/down arrow is computed fresh every render from price > previous, not stored as its own piece of state — one less thing that can drift out of sync with the value it describes.

On This Page