--- name: react-nextjs-patterns description: React and Next.js implementation patterns for performance and maintainability. Use when building frontend components, pages, and applications with React ecosystem. --- This skill provides React and Next.js specific patterns for building performant, maintainable frontend applications. ## When to Invoke This Skill Automatically activate for: - React component implementation - Next.js page and API routes - State management patterns - Performance optimization - Server/Client component decisions ## Next.js App Router Patterns ### Server vs Client Components ```tsx // Server Component (default) - data fetching, no interactivity // app/users/page.tsx export default async function UsersPage() { const users = await getUsers(); // Runs on server return (

Users

); } // Client Component - interactivity required // components/user-search.tsx 'use client'; import { useState } from 'react'; export function UserSearch({ onSearch }: { onSearch: (q: string) => void }) { const [query, setQuery] = useState(''); return ( setQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && onSearch(query)} /> ); } ``` ### Streaming with Suspense ```tsx // app/dashboard/page.tsx import { Suspense } from 'react'; export default function DashboardPage() { return (

Dashboard

{/* Fast data loads first */} }> {/* Slow data streams in */} }>
); } // Async component that streams async function StatsSection() { const stats = await getStats(); // Can be slow return ; } ``` ### Data Fetching Patterns ```tsx // Parallel data fetching async function DashboardPage() { // Fetch in parallel, not sequentially const [users, orders, stats] = await Promise.all([ getUsers(), getOrders(), getStats(), ]); return ; } // With error boundary import { notFound } from 'next/navigation'; async function UserPage({ params }: { params: { id: string } }) { const user = await getUser(params.id); if (!user) { notFound(); // Renders not-found.tsx } return ; } ``` ## React Performance Patterns ### Component Decomposition ```tsx // BAD: Large component with all state function BadUserList() { const [filter, setFilter] = useState(''); const [users, setUsers] = useState([]); const [selectedId, setSelectedId] = useState(null); // All users re-render on any state change return (
setFilter(e.target.value)} /> {users.map(user => (
setSelectedId(user.id)} className={selectedId === user.id ? 'selected' : ''} > {user.name}
))}
); } // GOOD: Push state down to where it's needed function GoodUserList() { const [users] = useState([]); return ; } function FilterableUserList({ users }: { users: User[] }) { const [filter, setFilter] = useState(''); const filtered = useMemo( () => users.filter(u => u.name.includes(filter)), [users, filter] ); return (
setFilter(e.target.value)} />
); } function SelectableList({ users }: { users: User[] }) { const [selectedId, setSelectedId] = useState(null); return users.map(user => ( setSelectedId(user.id)} /> )); } ``` ### Memoization Strategies ```tsx // Only memo when there's a measurable benefit const UserItem = memo(function UserItem({ user, selected, onSelect }: { user: User; selected: boolean; onSelect: () => void; }) { return (
{user.name}
); }); // useMemo for expensive computations function ExpensiveList({ items }: { items: Item[] }) { const processed = useMemo(() => { return items .filter(complexFilter) .sort(complexSort) .map(complexTransform); }, [items]); return ; } // useCallback for stable references passed to children function Parent() { const [items, setItems] = useState([]); const handleDelete = useCallback((id: string) => { setItems(prev => prev.filter(item => item.id !== id)); }, []); return ; } ``` ## State Management Patterns ### Context with Reducer ```tsx // types interface State { user: User | null; isLoading: boolean; error: Error | null; } type Action = | { type: 'FETCH_START' } | { type: 'FETCH_SUCCESS'; user: User } | { type: 'FETCH_ERROR'; error: Error } | { type: 'LOGOUT' }; // reducer function reducer(state: State, action: Action): State { switch (action.type) { case 'FETCH_START': return { ...state, isLoading: true, error: null }; case 'FETCH_SUCCESS': return { ...state, isLoading: false, user: action.user }; case 'FETCH_ERROR': return { ...state, isLoading: false, error: action.error }; case 'LOGOUT': return { ...state, user: null }; } } // context const AuthContext = createContext<{ state: State; dispatch: Dispatch; } | null>(null); // provider function AuthProvider({ children }: { children: ReactNode }) { const [state, dispatch] = useReducer(reducer, { user: null, isLoading: true, error: null, }); return ( {children} ); } // hook function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; } ``` ### Custom Hooks ```tsx // Data fetching hook function useQuery( key: string, fetcher: () => Promise ): { data: T | null; isLoading: boolean; error: Error | null; refetch: () => void } { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { setIsLoading(true); setError(null); try { const result = await fetcher(); setData(result); } catch (err) { setError(err instanceof Error ? err : new Error('Unknown error')); } finally { setIsLoading(false); } }, [fetcher]); useEffect(() => { fetchData(); }, [fetchData]); return { data, isLoading, error, refetch: fetchData }; } // Debounced value hook function useDebouncedValue(value: T, delay: number): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } // Local storage hook function useLocalStorage( key: string, initialValue: T ): [T, (value: T | ((prev: T) => T)) => void] { const [storedValue, setStoredValue] = useState(() => { if (typeof window === 'undefined') return initialValue; try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch { return initialValue; } }); const setValue = useCallback((value: T | ((prev: T) => T)) => { setStoredValue(prev => { const newValue = value instanceof Function ? value(prev) : value; localStorage.setItem(key, JSON.stringify(newValue)); return newValue; }); }, [key]); return [storedValue, setValue]; } ``` ## Component Patterns ### Compound Components ```tsx // Flexible API with compound components const Tabs = ({ children, defaultValue }: { children: ReactNode; defaultValue: string }) => { const [activeTab, setActiveTab] = useState(defaultValue); return (
{children}
); }; Tabs.List = function TabsList({ children }: { children: ReactNode }) { return
{children}
; }; Tabs.Tab = function Tab({ value, children }: { value: string; children: ReactNode }) { const { activeTab, setActiveTab } = useTabsContext(); return ( ); }; Tabs.Panel = function TabsPanel({ value, children }: { value: string; children: ReactNode }) { const { activeTab } = useTabsContext(); if (activeTab !== value) return null; return
{children}
; }; // Usage Tab 1 Tab 2 Content 1 Content 2 ``` ### Render Props & Children as Function ```tsx // Data provider with render prop function DataProvider({ fetcher, children, }: { fetcher: () => Promise; children: (data: T | null, isLoading: boolean) => ReactNode; }) { const { data, isLoading } = useQuery('data', fetcher); return <>{children(data, isLoading)}; } // Usage {(users, isLoading) => ( isLoading ? : )} ``` ## Best Practices Checklist - [ ] Use Server Components by default, Client Components only when needed - [ ] Push state down to the lowest component that needs it - [ ] Break large components into smaller, focused ones - [ ] Use Suspense boundaries for async operations - [ ] Memoize only when profiling shows benefit - [ ] Create custom hooks for reusable stateful logic - [ ] Use discriminated unions for component state - [ ] Implement proper error boundaries - [ ] Ensure accessibility (ARIA, keyboard navigation) - [ ] Use proper loading and error states