react-nextjs-patterns

This skill provides React and Next.js specific patterns for building performant, maintainable frontend applications.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-nextjs-patterns" with this command: npx skills add duyet/claude-plugins/duyet-claude-plugins-react-nextjs-patterns

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

// Server Component (default) - data fetching, no interactivity // app/users/page.tsx export default async function UsersPage() { const users = await getUsers(); // Runs on server

return ( <div> <h1>Users</h1> <UserList users={users} /> </div> ); }

// 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 ( <input value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && onSearch(query)} /> ); }

Streaming with Suspense

// app/dashboard/page.tsx import { Suspense } from 'react';

export default function DashboardPage() { return ( <div> <h1>Dashboard</h1>

  {/* Fast data loads first */}
  &#x3C;Suspense fallback={&#x3C;StatsSkeleton />}>
    &#x3C;StatsSection />
  &#x3C;/Suspense>

  {/* Slow data streams in */}
  &#x3C;Suspense fallback={&#x3C;ChartSkeleton />}>
    &#x3C;AnalyticsChart />
  &#x3C;/Suspense>
&#x3C;/div>

); }

// Async component that streams async function StatsSection() { const stats = await getStats(); // Can be slow return <Stats data={stats} />; }

Data Fetching Patterns

// Parallel data fetching async function DashboardPage() { // Fetch in parallel, not sequentially const [users, orders, stats] = await Promise.all([ getUsers(), getOrders(), getStats(), ]);

return <Dashboard users={users} orders={orders} stats={stats} />; }

// 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 <UserProfile user={user} />; }

React Performance Patterns

Component Decomposition

// BAD: Large component with all state function BadUserList() { const [filter, setFilter] = useState(''); const [users, setUsers] = useState<User[]>([]); const [selectedId, setSelectedId] = useState<string | null>(null);

// All users re-render on any state change return ( <div> <input value={filter} onChange={e => setFilter(e.target.value)} /> {users.map(user => ( <div key={user.id} onClick={() => setSelectedId(user.id)} className={selectedId === user.id ? 'selected' : ''} > {user.name} </div> ))} </div> ); }

// GOOD: Push state down to where it's needed function GoodUserList() { const [users] = useState<User[]>([]); return <FilterableUserList users={users} />; }

function FilterableUserList({ users }: { users: User[] }) { const [filter, setFilter] = useState(''); const filtered = useMemo( () => users.filter(u => u.name.includes(filter)), [users, filter] );

return ( <div> <input value={filter} onChange={e => setFilter(e.target.value)} /> <SelectableList users={filtered} /> </div> ); }

function SelectableList({ users }: { users: User[] }) { const [selectedId, setSelectedId] = useState<string | null>(null);

return users.map(user => ( <UserItem key={user.id} user={user} selected={selectedId === user.id} onSelect={() => setSelectedId(user.id)} /> )); }

Memoization Strategies

// Only memo when there's a measurable benefit const UserItem = memo(function UserItem({ user, selected, onSelect }: { user: User; selected: boolean; onSelect: () => void; }) { return ( <div onClick={onSelect} className={selected ? 'selected' : ''} > {user.name} </div> ); });

// useMemo for expensive computations function ExpensiveList({ items }: { items: Item[] }) { const processed = useMemo(() => { return items .filter(complexFilter) .sort(complexSort) .map(complexTransform); }, [items]);

return <List items={processed} />; }

// useCallback for stable references passed to children function Parent() { const [items, setItems] = useState<Item[]>([]);

const handleDelete = useCallback((id: string) => { setItems(prev => prev.filter(item => item.id !== id)); }, []);

return <ItemList items={items} onDelete={handleDelete} />; }

State Management Patterns

Context with Reducer

// 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<Action>; } | null>(null);

// provider function AuthProvider({ children }: { children: ReactNode }) { const [state, dispatch] = useReducer(reducer, { user: null, isLoading: true, error: null, });

return ( <AuthContext.Provider value={{ state, dispatch }}> {children} </AuthContext.Provider> ); }

// hook function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; }

Custom Hooks

// Data fetching hook function useQuery<T>( key: string, fetcher: () => Promise<T> ): { data: T | null; isLoading: boolean; error: Error | null; refetch: () => void } { const [data, setData] = useState<T | null>(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<Error | null>(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<T>(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<T>( key: string, initialValue: T ): [T, (value: T | ((prev: T) => T)) => void] { const [storedValue, setStoredValue] = useState<T>(() => { 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

// Flexible API with compound components const Tabs = ({ children, defaultValue }: { children: ReactNode; defaultValue: string }) => { const [activeTab, setActiveTab] = useState(defaultValue);

return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> <div className="tabs">{children}</div> </TabsContext.Provider> ); };

Tabs.List = function TabsList({ children }: { children: ReactNode }) { return <div className="tabs-list">{children}</div>; };

Tabs.Tab = function Tab({ value, children }: { value: string; children: ReactNode }) { const { activeTab, setActiveTab } = useTabsContext(); return ( <button className={activeTab === value ? 'active' : ''} onClick={() => setActiveTab(value)} > {children} </button> ); };

Tabs.Panel = function TabsPanel({ value, children }: { value: string; children: ReactNode }) { const { activeTab } = useTabsContext(); if (activeTab !== value) return null; return <div className="tabs-panel">{children}</div>; };

// Usage <Tabs defaultValue="tab1"> <Tabs.List> <Tabs.Tab value="tab1">Tab 1</Tabs.Tab> <Tabs.Tab value="tab2">Tab 2</Tabs.Tab> </Tabs.List> <Tabs.Panel value="tab1">Content 1</Tabs.Panel> <Tabs.Panel value="tab2">Content 2</Tabs.Panel> </Tabs>

Render Props & Children as Function

// Data provider with render prop function DataProvider<T>({ fetcher, children, }: { fetcher: () => Promise<T>; children: (data: T | null, isLoading: boolean) => ReactNode; }) { const { data, isLoading } = useQuery('data', fetcher); return <>{children(data, isLoading)}</>; }

// Usage <DataProvider fetcher={getUsers}> {(users, isLoading) => ( isLoading ? <Spinner /> : <UserList users={users} /> )} </DataProvider>

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

backend-api-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

gemini-prompting

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review