| 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
export default async function UsersPage() {
const users = await getUsers();
return (
<div>
<h1>Users</h1>
<UserList users={users} />
</div>
);
}
'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
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
{/* Fast data loads first */}
<Suspense fallback={<StatsSkeleton />}>
<StatsSection />
</Suspense>
{/* Slow data streams in */}
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
</div>
);
}
async function StatsSection() {
const stats = await getStats();
return <Stats data={stats} />;
}
Data Fetching Patterns
async function DashboardPage() {
const [users, orders, stats] = await Promise.all([
getUsers(),
getOrders(),
getStats(),
]);
return <Dashboard users={users} orders={orders} stats={stats} />;
}
import { notFound } from 'next/navigation';
async function UserPage({ params }: { params: { id: string } }) {
const user = await getUser(params.id);
if (!user) {
notFound();
}
return <UserProfile user={user} />;
}
React Performance Patterns
Component Decomposition
function BadUserList() {
const [filter, setFilter] = useState('');
const [users, setUsers] = useState<User[]>([]);
const [selectedId, setSelectedId] = useState<string | null>(null);
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>
);
}
function GoodUserList() {
const [users] = useState<User[]>([]);
return <FilterableUserList users={users} />;
}
function FilterableUserList({ users }: { users: User[] }) {
const [filter, setFilter] = ();
filtered = (
users.( u..(filter)),
[users, filter]
);
(
);
}
() {
[selectedId, setSelectedId] = useState< | >();
users.( (
));
}
Memoization Strategies
const UserItem = memo(function UserItem({
user,
selected,
onSelect
}: {
user: User;
selected: boolean;
onSelect: () => void;
}) {
return (
<div
onClick={onSelect}
className={selected ? 'selected' : ''}
>
{user.name}
</div>
);
});
function ExpensiveList({ items }: { items: Item[] }) {
const processed = useMemo(() => {
return items
.filter(complexFilter)
.sort(complexSort)
.map(complexTransform);
}, [items]);
return <List items={processed} />;
}
function Parent() {
const [items, setItems] = useState<Item[]>([]);
const handleDelete = useCallback((id: string) => {
( prev.( item. !== id));
}, []);
;
}
State Management Patterns
Context with Reducer
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' };
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, : action. };
:
{ ...state, : };
}
}
= createContext<{
: ;
: <>;
} | >();
() {
[state, dispatch] = (reducer, {
: ,
: ,
: ,
});
(
);
}
() {
context = ();
(!context) {
();
}
context;
}
Custom Hooks
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]);
{ data, isLoading, error, : fetchData };
}
useDebouncedValue<T>(: T, : ): T {
[debounced, setDebounced] = (value);
( {
timer = ( (value), delay);
(timer);
}, [value, delay]);
debounced;
}
useLocalStorage<T>(
: ,
: T
): [T, ] {
[storedValue, setStoredValue] = useState<T>( {
( === ) initialValue;
{
item = .(key);
item ? .(item) : initialValue;
} {
initialValue;
}
});
setValue = ( {
( {
newValue = value ? (prev) : value;
.(key, .(newValue));
newValue;
});
}, [key]);
[storedValue, setValue];
}
Component Patterns
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}
);
};
. = () {
{ activeTab } = ();
(activeTab !== value) ;
;
};
Render Props & Children as Function
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)}</>;
}
<DataProvider fetcher={getUsers}>
{(users, isLoading) => (
isLoading ? <Spinner /> : <UserList users={users} />
)}
</DataProvider>
Best Practices Checklist