Data table and data grid components built with shadcn/ui, featuring sorting, filtering, pagination, infinite scrolling, and real-time collaboration.
---
description: React patterns and best practices
globs:
alwaysApply: true
---
# React Patterns
## Code Style and Structure
- Write concise, technical TypeScript code with accurate examples.
- Use functional and declarative programming patterns; avoid classes.
- Prefer iteration and modularization over code duplication.
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
- Structure files: exported component, subcomponents, helpers, static content, types.
- Use console.log({ value }) instead of console.log(value)
- Use onCallback instead of handleCallback
- Use flex and gap instead of space-x-n and space-y-n
- Use cn to compose class names
- Just pass ref directly to the component because of react 19
## Naming Conventions
- Use lowercase with dashes for directories (e.g., components/auth-wizard).
- Favor named exports for components.
## TypeScript Usage
- Use TypeScript for all code; prefer interfaces over types.
- Avoid enums; use maps instead.
- Use functional components with TypeScript interfaces.
## Syntax and Formatting
- Use the "function" keyword for pure functions.
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.
- Use declarative JSX.
## UI and Styling
- Use Shadcn UI, Base UI, Radix, and Tailwind for components and styling.
- Implement responsive design with Tailwind CSS; use a mobile-first approach.
## Performance Optimization
- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC).
- Wrap client components in Suspense with fallback.
- Use dynamic loading for non-critical components.
- Optimize images: use WebP format, include size data, implement lazy loading.
## Key Conventions
- Optimize Web Vitals (LCP, CLS, FID).
- Limit 'use client':
- Favor server components and Next.js SSR.
- Use only for Web API access in small components.
- Avoid for data fetching or state management.
- Follow Next.js docs for Data Fetching, Rendering, and Routing.
Make sure not to start the dev server or run type of lint checking on agent mode.
## React Patterns and Best Practices
## Core Philosophy
- **UIs are thin wrappers over data**: avoid using local state (like useState) unless absolutely necessary and it's independent of business logic
- Even when local state seems needed, consider if you can flatten the UI state into a basic calculation
- useState is only necessary if it's truly reactive and cannot be derived
## State Management
- **Choose state machines over multiple useStates**: multiple useState calls make code harder to reason about
- Prefer a single state object with reducers for complex state logic
- Co-locate related state rather than spreading it across multiple useState calls
## Component Architecture
- **Create new component abstractions when nesting conditional logic**
- Move complex logic to new components rather than deeply nested conditionals
- Use ternaries only for small, easily readable logic
- Avoid top-level if/else statements in JSX; extract to components instead
## Side Effects and Dependencies
- **Avoid putting dependent logic in useEffects**: it causes misdirection about what the logic is doing
- Choose to explicitly define logic rather than depend on implicit reactive behavior
- When useEffect is necessary, be explicit about dependencies and cleanup
- Prefer derived state and event handlers over effect-driven logic
## Timing and Async Patterns
- **setTimeouts are flaky and usually a hack**: always provide a comment explaining why setTimeout is needed
- Consider alternatives like:
- Proper loading states
- Suspense boundaries
- Event-driven patterns
- State machines with delayed transitions
- requestAnimateFrame and queuMicrotask
## Code Quality Impact
These patterns prevent subtle bugs that pile up into major issues. While code may "work" without following these guidelines, violations often lead to:
- Hard-to-debug timing issues
- Unexpected re-renders
- State synchronization problems
- Complex refactoring requirements
## Examples
### โ Avoid: Multiple useState
```tsx
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
```
### โ
Prefer: State machine
```tsx
function useLazyRef<T>(fn: () => T) {
const ref = React.useRef<T | null>(null);
if (ref.current === null) {
ref.current = fn();
}
return ref as React.RefObject<T>;
}
interface Store<T> {
subscribe: (callback: () => void) => () => void
getState: () => T
setState: <K extends keyof T>(key: K, value: T[K]) => void
notify: () => void
}
function createStore<T>(
listenersRef: React.RefObject<Set<() => void>>,
stateRef: React.RefObject<T>,
onValueChange?: Partial<{
[K in keyof T]: (value: T[K], store: Store<T>) => void
}>
): Store<T> {
const store: Store<T> = {
subscribe: (cb) => {
listenersRef.current.add(cb);
return () => listenersRef.current.delete(cb);
},
getState: () => stateRef.current,
setState: (key, value) => {
if (Object.is(stateRef.current[key], value)) return;
stateRef.current[key] = value;
onValueChange?.[key]?.(value, store);
store.notify();
},
notify: () => {
for (const cb of listenersRef.current) {
cb();
}
},
};
return store;
}
function useStoreSelector<T, U>(
store: Store<T>,
selector: (state: T) => U
): U {
return React.useSyncExternalStore(
store.subscribe,
() => selector(store.getState()),
() => selector(store.getState()),
);
}
```
### โ Avoid: Complex conditionals in JSX
```tsx
return (
<div>
{user ? (
user.isAdmin ? (
<AdminPanel />
) : user.isPremium ? (
<PremiumDashboard />
) : (
<BasicDashboard />
)
) : (
<LoginForm />
)}
</div>
);
```
### โ
Prefer: Component abstraction
```tsx
function UserDashboard({ user }) {
if (!user) return <LoginForm />;
if (user.isAdmin) return <AdminPanel />;
if (user.isPremium) return <PremiumDashboard />;
return <BasicDashboard />;
}
```
### โ Avoid: Effect-driven logic
```tsx
useEffect(() => {
if (user && user.preferences) {
setTheme(user.preferences.theme);
}
}, [user]);
```
### โ
Prefer: Derived values
```tsx
const theme = user?.preferences?.theme ?? 'default';
```
## You Might Not Need an Effect
> Reference: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
Effects are an escape hatch for synchronizing with **external systems** (non-React widgets, network, browser DOM). If there is no external system involved, you shouldn't need an Effect.
### When to remove Effects
- **Don't transform data for rendering in Effects**: calculate at the top level of your component; it re-runs automatically when props/state change
- **Don't handle user events in Effects**: use the corresponding event handler where you know exactly what happened
- **Don't update state based on props or state**: derive it during rendering instead of using Effect + setState (avoids extra render passes)
- **Don't chain Effects** that each adjust state to trigger each other; calculate what you can during rendering and do the rest in event handlers
- **Don't notify parent components about state changes via Effects**: call the parent's callback in the same event handler that updates state, so React batches both updates in one pass
- **Don't pass data from child to parent via Effects**: lift the data fetching to the parent and pass it down as props
### When Effects are appropriate
- Synchronizing with external systems (jQuery widgets, browser APIs, network)
- Analytics events that fire because the component was **displayed** (not because of a user action)
- Data fetching (but always add cleanup to avoid race conditions, and prefer framework-level solutions)
### Patterns to use instead of Effects
| Instead of... | Do this |
| --- | --- |
| Effect + setState for derived values | `const fullName = firstName + ' ' + lastName;` |
| Effect to cache expensive calculations | `const result = useMemo(() => expensiveFn(a, b), [a, b]);` |
| Effect to reset state when a prop changes | Pass a `key` prop to force remounting |
| Effect to adjust partial state on prop change | Store the selected **ID** instead of the selected **item**, and derive the item during render |
| Effect to share logic between event handlers | Extract a plain function and call it from both handlers |
| Effect to subscribe to an external store | Use `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)` |
### โ Avoid: Resetting state via Effect
```tsx
useEffect(() => {
setComment('');
}, [userId]);
```
### โ
Prefer: Key-based reset
```tsx
<Profile userId={userId} key={userId} />
```
### โ Avoid: Storing derived state + syncing with Effect
```tsx
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
```
### โ
Prefer: Calculate during rendering
```tsx
const visibleTodos = getFilteredTodos(todos, filter);
```
### โ Avoid: Event-specific logic in an Effect
```tsx
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the cart!`);
}
}, [product]);
```
### โ
Prefer: Logic in the event handler
```tsx
function onProductBuy() {
addToCart(product);
showNotification(`Added ${product.name} to the cart!`);
}
```
### โ Avoid: Chained Effects
```tsx
useEffect(() => { if (card?.gold) setGoldCardCount(c => c + 1); }, [card]);
useEffect(() => { if (goldCardCount > 3) { setRound(r => r + 1); setGoldCardCount(0); } }, [goldCardCount]);
useEffect(() => { if (round > 5) setIsGameOver(true); }, [round]);
```
### โ
Prefer: Derive + compute in event handler
```tsx
const isGameOver = round > 5;
function onCardPlace(nextCard) {
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) setGoldCardCount(goldCardCount + 1);
else { setGoldCardCount(0); setRound(round + 1); }
}
}
```
### Data fetching: always add cleanup
```tsx
useEffect(() => {
let ignore = false;
fetchResults(query, page).then(json => {
if (!ignore) setResults(json);
});
return () => { ignore = true; };
}, [query, page]);
```
## useMemo
> Reference: [useMemo](https://react.dev/reference/react/useMemo)
`useMemo` caches the **result** of a calculation between re-renders.
### When to use useMemo
- The calculation is **noticeably slow** (~1ms+ measured with `console.time`) and its dependencies rarely change
- The result is passed as a prop to a `memo`-wrapped component (referential stability matters)
- The value is used as a dependency of another Hook (useMemo, useCallback, useEffect)
### When NOT to use useMemo
- The calculation is cheap; just compute it inline
- You're not passing the result to a memoized child or using it as a Hook dependency
- A dependency changes on every render anyway (memoization is broken)
### Prefer alternatives to useMemo
1. **Accept JSX as children**: wrapper components that accept `children` let React skip re-rendering children when wrapper state updates
2. **Keep state local**: don't lift transient state (forms, hover) higher than necessary
3. **Keep rendering pure**: if re-rendering causes bugs, fix the bug instead of adding memoization
4. **Remove unnecessary Effects** that update state, since chains of Effect-driven updates are the most common performance problem
5. **Move objects/functions inside Effects** instead of memoizing them outside
### โ Avoid: Effect + setState for derived computation
```tsx
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() => {
setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);
```
### โ
Prefer: Inline calculation (if cheap)
```tsx
const visibleTodos = getFilteredTodos(todos, filter);
```
### โ
Prefer: useMemo (if expensive)
```tsx
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
```
### โ Avoid: Object dependency that breaks memoization
```tsx
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => searchItems(allItems, searchOptions), [allItems, searchOptions]);
```
### โ
Prefer: Move object inside useMemo
```tsx
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]);
```
## useCallback
> Reference: [useCallback](https://react.dev/reference/react/useCallback)
`useCallback` caches the **function itself** between re-renders. It is equivalent to `useMemo(() => fn, deps)`.
### When to use useCallback
- Passing a function as a prop to a `memo`-wrapped component (so it can skip re-rendering)
- The function is used as a dependency of another Hook (useEffect, useMemo, useCallback)
- Writing a custom Hook that returns functions (always wrap returned functions in useCallback)
### When NOT to use useCallback
- The child component is not wrapped in `memo`; useCallback has no benefit
- A dependency changes on every render anyway
- The function is only used locally and not passed as a prop or Hook dependency
### โ Avoid: useCallback without memo on the child
```tsx
function Parent() {
const onClick = useCallback(() => { /* ... */ }, []);
return <Child onClick={onClick} />; // Child is NOT memo-wrapped, useCallback is pointless
}
```
### โ
Prefer: useCallback paired with memo
```tsx
const Child = memo(function Child({ onClick }) { /* ... */ });
function Parent() {
const onClick = useCallback(() => { /* ... */ }, [dep]);
return <Child onClick={onClick} />;
}
```
### โ
Prefer: Move functions inside Effects instead of wrapping in useCallback
```tsx
// Instead of useCallback + function dependency in Effect:
useEffect(() => {
function createOptions() {
return { serverUrl: 'https://localhost:1234', roomId };
}
const connection = createConnection(createOptions());
connection.connect();
return () => connection.disconnect();
}, [roomId]);
```
### Use updater functions to remove state dependencies
```tsx
// โ Depends on todos
const onTodoAdd = useCallback((text) => {
setTodos([...todos, { id: nextId++, text }]);
}, [todos]);
// โ
No dependency on todos
const onTodoAdd = useCallback((text) => {
setTodos(todos => [...todos, { id: nextId++, text }]);
}, []);
```
### Custom Hooks should wrap returned functions
```tsx
function useRouter() {
const { dispatch } = useContext(RouterStateContext);
const navigate = useCallback((url) => {
dispatch({ type: 'navigate', url });
}, [dispatch]);
return { navigate };
}
```