frontend-patterns

Frontend Development Patterns

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 "frontend-patterns" with this command: npx skills add webdevtodayjason/titanium-plugins/webdevtodayjason-titanium-plugins-frontend-patterns

Frontend Development Patterns

This skill provides comprehensive guidance for modern frontend development using React, Next.js, TypeScript, and related technologies.

Component Architecture

Component Composition Patterns

Container/Presentational Pattern:

// Presentational component (pure, reusable) interface UserCardProps { name: string; email: string; avatar: string; onEdit: () => void; }

function UserCard({ name, email, avatar, onEdit }: UserCardProps) { return ( <div className="user-card"> <img src={avatar} alt={name} /> <h3>{name}</h3> <p>{email}</p> <button onClick={onEdit}>Edit</button> </div> ); }

// Container component (handles logic, state, data fetching) function UserCardContainer({ userId }: { userId: string }) { const { data: user, isLoading } = useUser(userId); const { mutate: updateUser } = useUpdateUser();

if (isLoading) return <Skeleton />; if (!user) return <NotFound />;

return <UserCard {...user} onEdit={() => updateUser(user.id)} />; }

Compound Components Pattern:

// Flexible, composable API <Tabs defaultValue="profile"> <TabsList> <TabsTrigger value="profile">Profile</TabsTrigger> <TabsTrigger value="settings">Settings</TabsTrigger> </TabsList> <TabsContent value="profile"> <ProfileForm /> </TabsContent> <TabsContent value="settings"> <SettingsForm /> </TabsContent> </Tabs>

Component Organization

components/ ├── ui/ # Primitive components (buttons, inputs) │ ├── button.tsx │ ├── input.tsx │ └── card.tsx ├── forms/ # Form components │ ├── login-form.tsx │ └── register-form.tsx ├── features/ # Feature-specific components │ ├── user-profile/ │ │ ├── profile-header.tsx │ │ ├── profile-stats.tsx │ │ └── index.ts │ └── dashboard/ │ ├── dashboard-grid.tsx │ └── dashboard-card.tsx └── layouts/ # Layout components ├── main-layout.tsx └── auth-layout.tsx

State Management

Local State (useState)

Use for:

  • Component-specific UI state

  • Form inputs

  • Toggles, modals

function SearchBar() { const [query, setQuery] = useState(''); const [isOpen, setIsOpen] = useState(false);

return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} /> {isOpen && <SearchResults query={query} />} </div> ); }

Global State (Zustand)

Use for:

  • User authentication state

  • Theme preferences

  • Shopping cart

  • Cross-component shared state

import create from 'zustand';

interface UserStore { user: User | null; setUser: (user: User) => void; logout: () => void; }

export const useUserStore = create<UserStore>((set) => ({ user: null, setUser: (user) => set({ user }), logout: () => set({ user: null }), }));

// Usage function Header() { const user = useUserStore((state) => state.user); const logout = useUserStore((state) => state.logout);

return <div>{user ? user.name : 'Guest'}</div>; }

Server State (React Query / TanStack Query)

Use for:

  • API data fetching

  • Caching API responses

  • Optimistic updates

  • Background refetching

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Fetch data function UserProfile({ userId }: { userId: string }) { const { data, isLoading, error } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, // 5 minutes });

if (isLoading) return <Skeleton />; if (error) return <Error error={error} />;

return <div>{data.name}</div>; }

// Mutations with optimistic updates function useUpdateUser() { const queryClient = useQueryClient();

return useMutation({ mutationFn: (user: User) => api.updateUser(user), onMutate: async (newUser) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['user', newUser.id] });

  // Snapshot previous value
  const previous = queryClient.getQueryData(['user', newUser.id]);

  // Optimistically update
  queryClient.setQueryData(['user', newUser.id], newUser);

  return { previous };
},
onError: (err, newUser, context) => {
  // Rollback on error
  queryClient.setQueryData(['user', newUser.id], context?.previous);
},
onSettled: (newUser) => {
  // Refetch after mutation
  queryClient.invalidateQueries({ queryKey: ['user', newUser.id] });
},

}); }

Performance Optimization

  1. Memoization

useMemo (expensive calculations):

function ProductList({ products }: { products: Product[] }) { const sortedProducts = useMemo( () => products.sort((a, b) => b.price - a.price), [products] );

return <div>{sortedProducts.map(...)}</div>; }

useCallback (prevent re-renders):

function Parent() { const [count, setCount] = useState(0);

// ✅ Memoized - Child won't re-render unless count changes const handleClick = useCallback(() => { setCount(c => c + 1); }, []);

return <Child onClick={handleClick} />; }

const Child = memo(function Child({ onClick }: { onClick: () => void }) { console.log('Child rendered'); return <button onClick={onClick}>Click</button>; });

React.memo (prevent component re-renders):

const ExpensiveComponent = memo(function ExpensiveComponent({ data }) { // Only re-renders if data changes return <div>{/* expensive rendering */}</div>; });

  1. Code Splitting

Route-based splitting (Next.js automatic):

// app/dashboard/page.tsx - automatically code split export default function DashboardPage() { return <Dashboard />; }

Component-level splitting:

import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/heavy-chart'), { loading: () => <Skeleton />, ssr: false, // Don't render on server });

function Analytics() { return <HeavyChart data={chartData} />; }

  1. Image Optimization

import Image from 'next/image';

// ✅ Optimized - Next.js Image component <Image src="/hero.jpg" alt="Hero image" width={800} height={600} priority // Load immediately for LCP placeholder="blur" blurDataURL="data:image/..." />

// ❌ Not optimized <img src="/hero.jpg" alt="Hero" />

  1. Lazy Loading

import { lazy, Suspense } from 'react';

const Comments = lazy(() => import('./comments'));

function Post() { return ( <div> <PostContent /> <Suspense fallback={<CommentsSkeleton />}> <Comments postId={postId} /> </Suspense> </div> ); }

  1. Virtual Scrolling

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) { const parentRef = useRef<HTMLDivElement>(null);

const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 50, });

return ( <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}> <div style={{ height: ${virtualizer.getTotalSize()}px }}> {virtualizer.getVirtualItems().map((virtualRow) => ( <div key={virtualRow.index} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: ${virtualRow.size}px, transform: translateY(${virtualRow.start}px), }} > {items[virtualRow.index].name} </div> ))} </div> </div> ); }

Accessibility (a11y)

Semantic HTML

// ✅ Semantic <nav> <ul> <li><a href="/home">Home</a></li> <li><a href="/about">About</a></li> </ul> </nav>

// ❌ Non-semantic <div> <div> <div onClick={goHome}>Home</div> <div onClick={goAbout}>About</div> </div> </div>

ARIA Attributes

<button aria-label="Close dialog" aria-expanded={isOpen} aria-controls="dialog-content" onClick={toggle}

<X aria-hidden="true" /> </button>

<div id="dialog-content" role="dialog" aria-modal="true" aria-labelledby="dialog-title"

<h2 id="dialog-title">Dialog Title</h2> {content} </div>

Keyboard Navigation

function Dropdown() { const [isOpen, setIsOpen] = useState(false); const [focusedIndex, setFocusedIndex] = useState(0);

const handleKeyDown = (e: KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault(); setFocusedIndex((i) => Math.min(i + 1, items.length - 1)); break; case 'ArrowUp': e.preventDefault(); setFocusedIndex((i) => Math.max(i - 1, 0)); break; case 'Enter': selectItem(items[focusedIndex]); break; case 'Escape': setIsOpen(false); break; } };

return ( <div onKeyDown={handleKeyDown} role="combobox"> {/* dropdown content */} </div> ); }

Focus Management

import { useRef, useEffect } from 'react';

function Modal({ isOpen, onClose }: ModalProps) { const closeButtonRef = useRef<HTMLButtonElement>(null);

useEffect(() => { if (isOpen) { // Focus close button when modal opens closeButtonRef.current?.focus();

  // Trap focus within modal
  const handleTab = (e: KeyboardEvent) => {
    // Implement focus trap logic
  };

  document.addEventListener('keydown', handleTab);
  return () => document.removeEventListener('keydown', handleTab);
}

}, [isOpen]);

if (!isOpen) return null;

return ( <div role="dialog" aria-modal="true"> <button ref={closeButtonRef} onClick={onClose}> Close </button> {content} </div> ); }

Form Patterns

Controlled Forms with Validation

import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod';

const schema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(8, 'Password must be at least 8 characters'), age: z.number().min(18, 'Must be 18 or older'), });

type FormData = z.infer<typeof schema>;

function RegistrationForm() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({ resolver: zodResolver(schema), });

const onSubmit = async (data: FormData) => { await api.register(data); };

return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <input {...register('email')} type="email" /> {errors.email && <span>{errors.email.message}</span>} </div>

  &#x3C;div>
    &#x3C;input {...register('password')} type="password" />
    {errors.password &#x26;&#x26; &#x3C;span>{errors.password.message}&#x3C;/span>}
  &#x3C;/div>

  &#x3C;button type="submit" disabled={isSubmitting}>
    {isSubmitting ? 'Submitting...' : 'Submit'}
  &#x3C;/button>
&#x3C;/form>

); }

Form State Management

// Optimistic updates const { mutate } = useMutation({ mutationFn: updateUser, onMutate: async (newData) => { // Cancel outgoing queries await queryClient.cancelQueries({ queryKey: ['user', userId] });

// Snapshot previous
const previous = queryClient.getQueryData(['user', userId]);

// Optimistically update UI
queryClient.setQueryData(['user', userId], newData);

return { previous };

}, onError: (err, newData, context) => { // Rollback on error queryClient.setQueryData(['user', userId], context?.previous); toast.error('Update failed'); }, onSuccess: () => { toast.success('Updated successfully'); }, });

Error Handling

Error Boundaries

import { Component, ReactNode } from 'react';

interface Props { children: ReactNode; fallback?: ReactNode; }

interface State { hasError: boolean; error?: Error; }

class ErrorBoundary extends Component<Props, State> { constructor(props: Props) { super(props); this.state = { hasError: false }; }

static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; }

componentDidCatch(error: Error, errorInfo: any) { console.error('Error boundary caught:', error, errorInfo); // Log to error tracking service logErrorToService(error, errorInfo); }

render() { if (this.state.hasError) { return this.props.fallback || ( <div> <h2>Something went wrong</h2> <button onClick={() => this.setState({ hasError: false })}> Try again </button> </div> ); }

return this.props.children;

} }

// Usage <ErrorBoundary fallback={<ErrorFallback />}> <App /> </ErrorBoundary>

Async Error Handling

function DataComponent() { const { data, error, isError, isLoading } = useQuery({ queryKey: ['data'], queryFn: fetchData, retry: 3, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), });

if (isLoading) return <Skeleton />; if (isError) return <ErrorDisplay error={error} />;

return <DisplayData data={data} />; }

Responsive Design

Mobile-First Approach

// Tailwind CSS (mobile-first) <div className=" w-full /* Full width on mobile / md:w-1/2 / Half width on tablets / lg:w-1/3 / Third width on desktop / p-4 / Padding 16px / md:p-6 / Padding 24px on tablets+ */ "> Content </div>

Responsive Hooks

import { useMediaQuery } from '@/hooks/use-media-query';

function ResponsiveLayout() { const isMobile = useMediaQuery('(max-width: 768px)'); const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)'); const isDesktop = useMediaQuery('(min-width: 1025px)');

if (isMobile) return <MobileLayout />; if (isTablet) return <TabletLayout />; return <DesktopLayout />; }

Data Fetching Strategies

Server Components (Next.js 14+)

// app/users/page.tsx - Server Component async function UsersPage() { // Fetched on server const users = await db.user.findMany();

return <UserList users={users} />; }

Client Components with React Query

'use client';

function UserList() { const { data: users, isLoading } = useQuery({ queryKey: ['users'], queryFn: fetchUsers, });

if (isLoading) return <UsersLoading />;

return <div>{users.map(user => <UserCard key={user.id} {...user} />)}</div>; }

Parallel Data Fetching

function Dashboard() { const { data: user } = useQuery({ queryKey: ['user'], queryFn: fetchUser }); const { data: stats } = useQuery({ queryKey: ['stats'], queryFn: fetchStats }); const { data: posts } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });

// All three queries run in parallel return <div>...</div>; }

Dependent Queries

function UserPosts({ userId }: { userId: string }) { const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), });

const { data: posts } = useQuery({ queryKey: ['posts', user?.id], queryFn: () => fetchUserPosts(user!.id), enabled: !!user, // Only fetch after user is loaded });

return <div>...</div>; }

TypeScript Patterns

Prop Types

// Basic props interface ButtonProps { children: ReactNode; onClick: () => void; variant?: 'primary' | 'secondary'; disabled?: boolean; }

// Props with generic interface ListProps<T> { items: T[]; renderItem: (item: T) => ReactNode; keyExtractor: (item: T) => string; }

// Props extending HTML attributes interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { label: string; error?: string; }

Type-Safe API Responses

// API response types interface ApiResponse<T> { data: T; error?: never; }

interface ApiError { data?: never; error: { code: string; message: string; }; }

type ApiResult<T> = ApiResponse<T> | ApiError;

// Usage async function fetchUser(id: string): Promise<ApiResult<User>> { const response = await fetch(/api/users/${id}); return response.json(); }

Testing Patterns

Component Testing

import { render, screen, fireEvent } from '@testing-library/react'; import { LoginForm } from './login-form';

test('submits form with email and password', async () => { const onSubmit = jest.fn();

render(<LoginForm onSubmit={onSubmit} />);

fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'test@example.com' }, });

fireEvent.change(screen.getByLabelText('Password'), { target: { value: 'password123' }, });

fireEvent.click(screen.getByRole('button', { name: 'Login' }));

await waitFor(() => { expect(onSubmit).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123', }); }); });

Mock API Calls

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { rest } from 'msw'; import { setupServer } from 'msw/node';

const server = setupServer( rest.get('/api/users/:id', (req, res, ctx) => { return res(ctx.json({ id: req.params.id, name: 'Test User', email: 'test@example.com', })); }) );

beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close());

test('displays user data', async () => { const queryClient = new QueryClient();

render( <QueryClientProvider client={queryClient}> <UserProfile userId="123" /> </QueryClientProvider> );

expect(await screen.findByText('Test User')).toBeInTheDocument(); });

Build Optimization

Bundle Analysis

Next.js bundle analyzer

npm install @next/bundle-analyzer

next.config.js

const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', });

module.exports = withBundleAnalyzer({ // config });

Run analysis

ANALYZE=true npm run build

Tree Shaking

// ✅ Named imports (tree-shakeable) import { Button } from '@/components/ui/button';

// ❌ Namespace import (includes everything) import * as UI from '@/components/ui';

Dynamic Imports

// Import only when needed async function handleExport() { const { exportToPDF } = await import('@/lib/pdf-export'); await exportToPDF(data); }

Common Frontend Mistakes to Avoid

  • Prop drilling: Use Context or state management library instead

  • Unnecessary re-renders: Use memo, useMemo, useCallback appropriately

  • Missing loading states: Always show loading indicators

  • No error boundaries: Catch errors before they break the app

  • Inline functions in JSX: Causes re-renders, use useCallback

  • Large bundle sizes: Code split and lazy load

  • Missing alt text: All images need descriptive alt text

  • Inaccessible forms: Use proper labels and ARIA

  • Console.log in production: Remove or use proper logging

  • Mixing server and client code: Know Next.js boundaries

Performance Metrics (Core Web Vitals)

LCP (Largest Contentful Paint)

Target: < 2.5 seconds

Optimize:

  • Preload critical images

  • Use Next.js Image component

  • Minimize render-blocking resources

  • Use CDN for assets

FID (First Input Delay)

Target: < 100 milliseconds

Optimize:

  • Minimize JavaScript execution

  • Code split large bundles

  • Use web workers for heavy computation

  • Defer non-critical JavaScript

CLS (Cumulative Layout Shift)

Target: < 0.1

Optimize:

  • Set explicit width/height on images

  • Reserve space for ads/embeds

  • Avoid inserting content above existing content

  • Use CSS transforms instead of layout properties

When to Use This Skill

Use this skill when:

  • Building React or Next.js components

  • Implementing frontend features

  • Optimizing frontend performance

  • Debugging rendering issues

  • Setting up state management

  • Implementing forms

  • Ensuring accessibility

  • Working with responsive design

  • Fetching and caching data

  • Testing frontend code

Remember: Modern frontend development is about creating fast, accessible, and delightful user experiences. Follow these patterns to build UIs that users love.

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.

Coding

api-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

frontend-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

frontend-patterns

No summary provided by upstream source.

Repository SourceNeeds Review