frontend-modern

Modern 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-modern" with this command: npx skills add twofoldtech-dakota/claude-marketplace/twofoldtech-dakota-claude-marketplace-frontend-modern

Modern Frontend Development Patterns

React Component Patterns

Functional Components

import { useState, useEffect } from 'react';

interface CardProps { title: string; description: string; imageUrl?: string; onClick?: () => void; }

export const Card = ({ title, description, imageUrl, onClick }: CardProps) => { return ( <article className="card" onClick={onClick}> {imageUrl && ( <img src={imageUrl} alt={title} className="card__image" /> )} <div className="card__content"> <h3 className="card__title">{title}</h3> <p className="card__description">{description}</p> </div> </article> ); };

Component with Children

interface ContainerProps { children: React.ReactNode; className?: string; as?: keyof JSX.IntrinsicElements; }

export const Container = ({ children, className = '', as: Component = 'div' }: ContainerProps) => { return ( <Component className={container ${className}}> {children} </Component> ); };

Compound Components

interface AccordionContextType { openItems: string[]; toggle: (id: string) => void; }

const AccordionContext = createContext<AccordionContextType | null>(null);

export const Accordion = ({ children }: { children: React.ReactNode }) => { const [openItems, setOpenItems] = useState<string[]>([]);

const toggle = (id: string) => { setOpenItems(prev => prev.includes(id) ? prev.filter(item => item !== id) : [...prev, id] ); };

return ( <AccordionContext.Provider value={{ openItems, toggle }}> <div className="accordion">{children}</div> </AccordionContext.Provider> ); };

Accordion.Item = ({ id, title, children }: { id: string; title: string; children: React.ReactNode; }) => { const context = useContext(AccordionContext); if (!context) throw new Error('Accordion.Item must be used within Accordion');

const isOpen = context.openItems.includes(id);

return ( <div className="accordion__item"> <button className="accordion__trigger" onClick={() => context.toggle(id)} aria-expanded={isOpen} > {title} </button> {isOpen && ( <div className="accordion__content">{children}</div> )} </div> ); };

React Hooks

useState

// Simple state const [count, setCount] = useState(0);

// Object state const [formData, setFormData] = useState({ name: '', email: '', message: '' });

// Update object state immutably const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); };

// Functional update (when new state depends on previous) setCount(prev => prev + 1);

useEffect

// Run on mount only useEffect(() => { console.log('Component mounted'); return () => console.log('Component unmounted'); }, []);

// Run when dependency changes useEffect(() => { fetchData(userId); }, [userId]);

// Cleanup pattern useEffect(() => { const controller = new AbortController();

fetch('/api/data', { signal: controller.signal }) .then(res => res.json()) .then(setData);

return () => controller.abort(); }, []);

useMemo and useCallback

// Memoize expensive computation const sortedItems = useMemo(() => { return items.sort((a, b) => a.name.localeCompare(b.name)); }, [items]);

// Memoize callback for child components const handleClick = useCallback((id: string) => { setSelectedId(id); }, []);

// Pass to optimized child <List items={items} onItemClick={handleClick} />

Custom Hooks

// useLocalStorage function useLocalStorage<T>(key: string, initialValue: T) { const [storedValue, setStoredValue] = useState<T>(() => { if (typeof window === 'undefined') return initialValue;

try {
  const item = window.localStorage.getItem(key);
  return item ? JSON.parse(item) : initialValue;
} catch {
  return initialValue;
}

});

const setValue = (value: T | ((val: T) => T)) => { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); window.localStorage.setItem(key, JSON.stringify(valueToStore)); };

return [storedValue, setValue] as const; }

// useDebounce function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => { const timer = setTimeout(() => setDebouncedValue(value), delay); return () => clearTimeout(timer); }, [value, delay]);

return debouncedValue; }

// useFetch function useFetch<T>(url: string) { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<Error | null>(null);

useEffect(() => { const controller = new AbortController();

setLoading(true);
fetch(url, { signal: controller.signal })
  .then(res => {
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  })
  .then(setData)
  .catch(err => {
    if (err.name !== 'AbortError') setError(err);
  })
  .finally(() => setLoading(false));

return () => controller.abort();

}, [url]);

return { data, loading, error }; }

Next.js Patterns

Page Components (App Router)

// app/products/page.tsx import { getProducts } from '@/lib/api';

export default async function ProductsPage() { const products = await getProducts();

return ( <main> <h1>Products</h1> <ProductList products={products} /> </main> ); }

// Metadata export const metadata = { title: 'Products', description: 'Browse our product catalog', };

Dynamic Routes

// app/products/[slug]/page.tsx import { getProduct, getProducts } from '@/lib/api'; import { notFound } from 'next/navigation';

interface PageProps { params: { slug: string }; }

export default async function ProductPage({ params }: PageProps) { const product = await getProduct(params.slug);

if (!product) { notFound(); }

return <ProductDetail product={product} />; }

// Generate static params at build time export async function generateStaticParams() { const products = await getProducts(); return products.map(product => ({ slug: product.slug })); }

// Dynamic metadata export async function generateMetadata({ params }: PageProps) { const product = await getProduct(params.slug);

return { title: product?.name ?? 'Product Not Found', description: product?.description, }; }

Server vs Client Components

// Server Component (default) - can fetch data directly // app/components/ProductList.tsx import { getProducts } from '@/lib/api';

export async function ProductList() { const products = await getProducts(); // Direct data fetch

return ( <ul> {products.map(product => ( <li key={product.id}>{product.name}</li> ))} </ul> ); }

// Client Component - for interactivity // app/components/AddToCartButton.tsx 'use client';

import { useState } from 'react';

export function AddToCartButton({ productId }: { productId: string }) { const [isLoading, setIsLoading] = useState(false);

const handleClick = async () => { setIsLoading(true); await addToCart(productId); setIsLoading(false); };

return ( <button onClick={handleClick} disabled={isLoading}> {isLoading ? 'Adding...' : 'Add to Cart'} </button> ); }

API Routes

// app/api/products/route.ts import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const category = searchParams.get('category');

const products = await db.products.findMany({ where: category ? { category } : undefined, });

return NextResponse.json(products); }

export async function POST(request: NextRequest) { const body = await request.json();

const product = await db.products.create({ data: body, });

return NextResponse.json(product, { status: 201 }); }

Vue.js Patterns

Composition API

<script setup lang="ts"> import { ref, computed, onMounted } from 'vue';

interface Product { id: string; name: string; price: number; }

const props = defineProps<{ initialProducts: Product[]; }>();

const emit = defineEmits<{ (e: 'select', product: Product): void; }>();

const products = ref<Product[]>(props.initialProducts); const searchQuery = ref('');

const filteredProducts = computed(() => { return products.value.filter(p => p.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ); });

const handleSelect = (product: Product) => { emit('select', product); };

onMounted(() => { console.log('Component mounted'); }); </script>

<template> <div class="product-list"> <input v-model="searchQuery" placeholder="Search products..." /> <ul> <li v-for="product in filteredProducts" :key="product.id" @click="handleSelect(product)" > {{ product.name }} - ${{ product.price }} </li> </ul> </div> </template>

Composables

// composables/useProducts.ts import { ref, onMounted } from 'vue';

export function useProducts() { const products = ref<Product[]>([]); const loading = ref(true); const error = ref<Error | null>(null);

const fetchProducts = async () => { loading.value = true; try { const response = await fetch('/api/products'); products.value = await response.json(); } catch (e) { error.value = e as Error; } finally { loading.value = false; } };

onMounted(fetchProducts);

return { products, loading, error, refetch: fetchProducts, }; }

TypeScript Patterns

Interface vs Type

// Interfaces - extensible, good for objects interface User { id: string; name: string; email: string; }

interface Admin extends User { role: 'admin'; permissions: string[]; }

// Types - for unions, intersections, primitives type Status = 'pending' | 'active' | 'completed'; type ID = string | number; type UserWithStatus = User & { status: Status };

Generics

// Generic function function getFirst<T>(items: T[]): T | undefined { return items[0]; }

// Generic interface interface ApiResponse<T> { data: T; status: number; message: string; }

// Generic component props interface ListProps<T> { items: T[]; renderItem: (item: T) => React.ReactNode; keyExtractor: (item: T) => string; }

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) { return ( <ul> {items.map(item => ( <li key={keyExtractor(item)}>{renderItem(item)}</li> ))} </ul> ); }

Utility Types

// Pick - select specific properties type UserPreview = Pick<User, 'id' | 'name'>;

// Omit - exclude properties type UserWithoutEmail = Omit<User, 'email'>;

// Partial - make all properties optional type UserUpdate = Partial<User>;

// Required - make all properties required type RequiredUser = Required<User>;

// Record - object with specific key/value types type UserMap = Record<string, User>;

// Readonly - make all properties readonly type ImmutableUser = Readonly<User>;

Modern CSS

CSS Modules

// styles/Button.module.css .button { padding: 0.5rem 1rem; border-radius: 4px; }

.primary { background: var(--color-primary); color: white; }

.secondary { background: transparent; border: 1px solid var(--color-primary); }

// Button.tsx import styles from './Button.module.css';

export const Button = ({ variant = 'primary', children }) => ( <button className={${styles.button} ${styles[variant]}}> {children} </button> );

Tailwind CSS

// Utility classes export const Card = ({ title, children }) => ( <div className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow"> <h3 className="text-xl font-semibold text-gray-900 mb-4">{title}</h3> <div className="text-gray-600">{children}</div> </div> );

// With @apply in CSS /* styles.css */ .btn { @apply px-4 py-2 rounded font-medium transition-colors; }

.btn-primary { @apply bg-blue-600 text-white hover:bg-blue-700; }

CSS Variables

:root { /* Colors */ --color-primary: #0066cc; --color-primary-dark: #004999; --color-secondary: #6c757d;

/* Typography */ --font-family-base: 'Inter', sans-serif; --font-size-base: 1rem;

/* Spacing */ --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 2rem;

/* Shadows */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); }

/* Dark mode */ @media (prefers-color-scheme: dark) { :root { --color-primary: #4da6ff; --color-background: #1a1a1a; --color-text: #ffffff; } }

State Management

React Context

// context/ThemeContext.tsx interface ThemeContextType { theme: 'light' | 'dark'; toggleTheme: () => void; }

const ThemeContext = createContext<ThemeContextType | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<'light' | 'dark'>('light');

const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); };

return ( <ThemeContext.Provider value={{ theme, toggleTheme }}> {children} </ThemeContext.Provider> ); }

export function useTheme() { const context = useContext(ThemeContext); if (!context) throw new Error('useTheme must be used within ThemeProvider'); return context; }

Zustand (Lightweight State)

import { create } from 'zustand';

interface CartState { items: CartItem[]; addItem: (item: CartItem) => void; removeItem: (id: string) => void; clearCart: () => void; total: () => number; }

export const useCart = create<CartState>((set, get) => ({ items: [],

addItem: (item) => set((state) => ({ items: [...state.items, item] })),

removeItem: (id) => set((state) => ({ items: state.items.filter(item => item.id !== id) })),

clearCart: () => set({ items: [] }),

total: () => get().items.reduce((sum, item) => sum + item.price, 0), }));

Data Fetching

React Query / TanStack Query

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

// Fetch query function useProducts() { return useQuery({ queryKey: ['products'], queryFn: () => fetch('/api/products').then(res => res.json()), staleTime: 5 * 60 * 1000, // 5 minutes }); }

// Mutation with cache invalidation function useCreateProduct() { const queryClient = useQueryClient();

return useMutation({ mutationFn: (newProduct: CreateProductInput) => fetch('/api/products', { method: 'POST', body: JSON.stringify(newProduct), }).then(res => res.json()), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['products'] }); }, }); }

SWR

import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

function useProducts() { const { data, error, isLoading, mutate } = useSWR('/api/products', fetcher, { revalidateOnFocus: true, refreshInterval: 30000, });

return { products: data, isLoading, isError: error, refresh: mutate, }; }

Error Handling

Error Boundaries

'use client';

import { Component, ReactNode } from 'react';

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

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

export class ErrorBoundary extends Component<Props, State> { state: State = { hasError: false };

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

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error('Error caught:', error, errorInfo); // Send to error tracking service }

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

Async Error Handling

// With try-catch async function fetchData() { try { const response = await fetch('/api/data'); if (!response.ok) { throw new Error(HTTP ${response.status}); } return await response.json(); } catch (error) { if (error instanceof Error) { console.error('Fetch failed:', error.message); } throw error; } }

// Result pattern type Result<T, E = Error> = | { success: true; data: T } | { success: false; error: E };

async function safeFetch<T>(url: string): Promise<Result<T>> { try { const response = await fetch(url); if (!response.ok) { return { success: false, error: new Error(HTTP ${response.status}) }; } const data = await response.json(); return { success: true, data }; } catch (error) { return { success: false, error: error as Error }; } }

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

umbraco-development

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-razor

No summary provided by upstream source.

Repository SourceNeeds Review
General

optimizely-content-cloud

No summary provided by upstream source.

Repository SourceNeeds Review
General

optimizely-experimentation

No summary provided by upstream source.

Repository SourceNeeds Review