| description | globs | alwaysApply |
|---|---|---|
React-specific standards – async, bundle, memo, state, rendering, Suspense, Error Boundaries |
**/*.tsx, **/*.jsx |
false |
Apply only when working with React (TSX/JSX). General JS/TS rules are in js-ts-react-standards.mdc.
Promise.all()for independent ops (no waterfalls)- Prefer Suspense for data loading when possible; defer await to where needed; streaming where supported
- SWR or similar for client-side data fetching/cache
- Direct imports; avoid barrel files when possible
React.lazy+Suspensefor heavy components (code-split)- Analytics/logging after hydration
- Preload on hover/focus
- Prefer simple computed constants in render over
useMemowhen the work is cheap (e.g.const fullName = user.firstName + ' ' + user.lastName) - Use
useMemoonly when: the computation is expensive, or you need referential stability (e.g. object in a dependency array, or child is memoized and receives the value) - Use
useCallbackonly when: the function is passed to a memoized child or used in a dependency array that would otherwise change every render - Avoid wrapping every value/callback in
useMemo/useCallback; measure before optimizing
// ✅ cheap derived value – no useMemo
const sortedItems = [...items].sort(byDate);
const isEmpty = items.length === 0;
// ✅ useMemo when expensive or referentially sensitive
const chartData = useMemo(() => computeChartData(rawData), [rawData]);- Function in useState for expensive init:
useState(() => compute()) - Functional setState for stable callbacks
- useTransition for loading/non-urgent updates
- Derive state in render, not effects
- Don't subscribe to state only used in callbacks
- Ternary for conditionals, not
&&(avoids 0/falsy bugs) - Hoist static JSX outside components
- content-visibility for long lists
- useTransition over loading spinners
- Set/Map for O(1) lookups; toSorted() for immutability
- Hoist RegExp outside loops; cache props in loops
- Return early; combine filter+map into one pass
- Passive listeners for scroll events
-
Error Boundary (what it is): A React component (class or wrapper lib) that catches errors that happen during render or lifecycle of its children. Instead of the whole app going blank, only that part of the tree shows a fallback UI (e.g. “Something went wrong”). The rest of the app keeps working.
-
Error Boundaries (when to use): Wrap each “section” of the app (e.g. sidebar, main content, a feature) so one failure doesn’t crash everything. Use them for errors in render/effects. For errors inside event handlers or async code, use normal try/catch—Error Boundaries don’t catch those.
-
Suspense: Use it to declare “this part is loading” at a boundary (e.g. around a route or a block of components). React can show a fallback (spinner/skeleton) there and stream the content when ready. Prefer this over spreading
isLoadingbooleans in many components; one boundary often replaces several loading flags. -
Loading and error in data flows: Every place that fetches data should handle both “loading” and “error.” Either use Suspense + Error Boundary (declarative) or explicit state (e.g.
loading,error) and render accordingly. Never leave the user with a blank screen or no feedback. -
List keys: React uses the
keyprop to know which item is which when the list changes. Use a stable, unique id from the data (e.g.item.id). Avoid using the array index as key when the list can be reordered, filtered, or items can be added/removed—otherwise React can reuse the wrong component and cause bugs or lost state.