Frontend Patterns

affaan-m/ECC/docs/es/skills/frontend-patterns

by affaan-mef648e01899ba3e8dc6371642deaaf64b4477775No license275K starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 4 days ago

Patrones de desarrollo frontend para React, Next.js, gestión de estado, optimización de rendimiento y buenas prácticas de UI.

Instructions onlySoftware Development
AI-generated overview

Reference patterns for React and Next.js frontend development, covering components, state, performance, forms and accessibility.

What it does
This skill supplies a reference guide of modern frontend patterns for React, Next.js and high-performance user interfaces. It presents code examples for component composition, compound components, render props, custom hooks, state management with Context and Reducer, memoization, code splitting, list virtualization, form validation, error boundaries, animations and accessibility. It produces guidance and example code rather than generated files or runnable tooling.
When to use it
Use it when building React components, managing state, implementing data fetching, optimizing rendering performance, handling forms, or adding accessible and responsive UI behavior. It is meant as a pattern reference during frontend implementation work.
Requirements
No scripts or assets ship with the skill; it is instructions and code examples only. Following the examples assumes a React or Next.js project and libraries such as Zustand, SWR, React Query, Zod, TanStack Virtual and Framer Motion, but nothing is installed or executed by the skill itself.

Patrones de Desarrollo Frontend

Patrones modernos de frontend para React, Next.js e interfaces de usuario de alto rendimiento.

Cuándo Activar

  • Construir componentes React (composición, props, renderizado)
  • Gestionar estado (useState, useReducer, Zustand, Context)
  • Implementar obtención de datos (SWR, React Query, server components)
  • Optimizar rendimiento (memoización, virtualización, code splitting)
  • Trabajar con formularios (validación, inputs controlados, esquemas Zod)
  • Manejar routing y navegación del lado del cliente
  • Construir patrones de UI accesibles y responsivos

Patrones de Componentes

Composición sobre Herencia

typescript
// PASS: BIEN: Composición de componentesinterface CardProps {  children: React.ReactNode  variant?: 'default' | 'outlined'}
export function Card({ children, variant = 'default' }: CardProps) {  return <div className={`card card-${variant}`}>{children}</div>}
export function CardHeader({ children }: { children: React.ReactNode }) {  return <div className="card-header">{children}</div>}
export function CardBody({ children }: { children: React.ReactNode }) {  return <div className="card-body">{children}</div>}
// Uso<Card>  <CardHeader>Title</CardHeader>  <CardBody>Content</CardBody></Card>

Compound Components

typescript
interface TabsContextValue {  activeTab: string  setActiveTab: (tab: string) => void}
const TabsContext = createContext<TabsContextValue | undefined>(undefined)
export function Tabs({ children, defaultTab }: {  children: React.ReactNode  defaultTab: string}) {  const [activeTab, setActiveTab] = useState(defaultTab)
  return (    <TabsContext.Provider value={{ activeTab, setActiveTab }}>      {children}    </TabsContext.Provider>  )}
export function TabList({ children }: { children: React.ReactNode }) {  return <div className="tab-list">{children}</div>}
export function Tab({ id, children }: { id: string, children: React.ReactNode }) {  const context = useContext(TabsContext)  if (!context) throw new Error('Tab must be used within Tabs')
  return (    <button      className={context.activeTab === id ? 'active' : ''}      onClick={() => context.setActiveTab(id)}    >      {children}    </button>  )}
// Uso<Tabs defaultTab="overview">  <TabList>    <Tab id="overview">Overview</Tab>    <Tab id="details">Details</Tab>  </TabList></Tabs>

Patrón Render Props

typescript
interface DataLoaderProps<T> {  url: string  children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode}
export function DataLoader<T>({ url, children }: DataLoaderProps<T>) {  const [data, setData] = useState<T | null>(null)  const [loading, setLoading] = useState(true)  const [error, setError] = useState<Error | null>(null)
  useEffect(() => {    fetch(url)      .then(res => res.json())      .then(setData)      .catch(setError)      .finally(() => setLoading(false))  }, [url])
  return <>{children(data, loading, error)}</>}
// Uso<DataLoader<Market[]> url="/api/markets">  {(markets, loading, error) => {    if (loading) return <Spinner />    if (error) return <Error error={error} />    return <MarketList markets={markets!} />  }}</DataLoader>

Patrones de Custom Hooks

Hook de Gestión de Estado

typescript
export function useToggle(initialValue = false): [boolean, () => void] {  const [value, setValue] = useState(initialValue)
  const toggle = useCallback(() => {    setValue(v => !v)  }, [])
  return [value, toggle]}
// Usoconst [isOpen, toggleOpen] = useToggle()

Hook de Obtención de Datos Asíncrona

typescript
interface UseQueryOptions<T> {  onSuccess?: (data: T) => void  onError?: (error: Error) => void  enabled?: boolean}
export function useQuery<T>(  key: string,  fetcher: () => Promise<T>,  options?: UseQueryOptions<T>) {  const [data, setData] = useState<T | null>(null)  const [error, setError] = useState<Error | null>(null)  const [loading, setLoading] = useState(false)
  const refetch = useCallback(async () => {    setLoading(true)    setError(null)
    try {      const result = await fetcher()      setData(result)      options?.onSuccess?.(result)    } catch (err) {      const error = err as Error      setError(error)      options?.onError?.(error)    } finally {      setLoading(false)    }  }, [fetcher, options])
  useEffect(() => {    if (options?.enabled !== false) {      refetch()    }  }, [key, refetch, options?.enabled])
  return { data, error, loading, refetch }}
// Usoconst { data: markets, loading, error, refetch } = useQuery(  'markets',  () => fetch('/api/markets').then(r => r.json()),  {    onSuccess: data => console.log('Fetched', data.length, 'markets'),    onError: err => console.error('Failed:', err)  })

Hook de Debounce

typescript
export function useDebounce<T>(value: T, delay: number): T {  const [debouncedValue, setDebouncedValue] = useState<T>(value)
  useEffect(() => {    const handler = setTimeout(() => {      setDebouncedValue(value)    }, delay)
    return () => clearTimeout(handler)  }, [value, delay])
  return debouncedValue}
// Usoconst [searchQuery, setSearchQuery] = useState('')const debouncedQuery = useDebounce(searchQuery, 500)
useEffect(() => {  if (debouncedQuery) {    performSearch(debouncedQuery)  }}, [debouncedQuery])

Patrones de Gestión de Estado

Patrón Context + Reducer

typescript
interface State {  markets: Market[]  selectedMarket: Market | null  loading: boolean}
type Action =  | { type: 'SET_MARKETS'; payload: Market[] }  | { type: 'SELECT_MARKET'; payload: Market }  | { type: 'SET_LOADING'; payload: boolean }
function reducer(state: State, action: Action): State {  switch (action.type) {    case 'SET_MARKETS':      return { ...state, markets: action.payload }    case 'SELECT_MARKET':      return { ...state, selectedMarket: action.payload }    case 'SET_LOADING':      return { ...state, loading: action.payload }    default:      return state  }}
const MarketContext = createContext<{  state: State  dispatch: Dispatch<Action>} | undefined>(undefined)
export function MarketProvider({ children }: { children: React.ReactNode }) {  const [state, dispatch] = useReducer(reducer, {    markets: [],    selectedMarket: null,    loading: false  })
  return (    <MarketContext.Provider value={{ state, dispatch }}>      {children}    </MarketContext.Provider>  )}
export function useMarkets() {  const context = useContext(MarketContext)  if (!context) throw new Error('useMarkets must be used within MarketProvider')  return context}

Optimización de Rendimiento

Memoización

typescript
// PASS: useMemo para cómputos costososconst sortedMarkets = useMemo(() => {  return markets.sort((a, b) => b.volume - a.volume)}, [markets])
// PASS: useCallback para funciones pasadas a hijosconst handleSearch = useCallback((query: string) => {  setSearchQuery(query)}, [])
// PASS: React.memo para componentes purosexport const MarketCard = React.memo<MarketCardProps>(({ market }) => {  return (    <div className="market-card">      <h3>{market.name}</h3>      <p>{market.description}</p>    </div>  )})

Code Splitting y Carga Diferida

typescript
import { lazy, Suspense } from 'react'
// PASS: Carga diferida de componentes pesadosconst HeavyChart = lazy(() => import('./HeavyChart'))const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
export function Dashboard() {  return (    <div>      <Suspense fallback={<ChartSkeleton />}>        <HeavyChart data={data} />      </Suspense>
      <Suspense fallback={null}>        <ThreeJsBackground />      </Suspense>    </div>  )}

Virtualización para Listas Largas

typescript
import { useVirtualizer } from '@tanstack/react-virtual'
export function VirtualMarketList({ markets }: { markets: Market[] }) {  const parentRef = useRef<HTMLDivElement>(null)
  const virtualizer = useVirtualizer({    count: markets.length,    getScrollElement: () => parentRef.current,    estimateSize: () => 100,  // Altura estimada de fila    overscan: 5  // Elementos extra a renderizar  })
  return (    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>      <div        style={{          height: `${virtualizer.getTotalSize()}px`,          position: 'relative'        }}      >        {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)`            }}          >            <MarketCard market={markets[virtualRow.index]} />          </div>        ))}      </div>    </div>  )}

Patrones de Manejo de Formularios

Formulario Controlado con Validación

typescript
interface FormData {  name: string  description: string  endDate: string}
interface FormErrors {  name?: string  description?: string  endDate?: string}
export function CreateMarketForm() {  const [formData, setFormData] = useState<FormData>({    name: '',    description: '',    endDate: ''  })
  const [errors, setErrors] = useState<FormErrors>({})
  const validate = (): boolean => {    const newErrors: FormErrors = {}
    if (!formData.name.trim()) {      newErrors.name = 'Name is required'    } else if (formData.name.length > 200) {      newErrors.name = 'Name must be under 200 characters'    }
    if (!formData.description.trim()) {      newErrors.description = 'Description is required'    }
    if (!formData.endDate) {      newErrors.endDate = 'End date is required'    }
    setErrors(newErrors)    return Object.keys(newErrors).length === 0  }
  const handleSubmit = async (e: React.FormEvent) => {    e.preventDefault()
    if (!validate()) return
    try {      await createMarket(formData)      // Manejo de éxito    } catch (error) {      // Manejo de error    }  }
  return (    <form onSubmit={handleSubmit}>      <input        value={formData.name}        onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}        placeholder="Market name"      />      {errors.name && <span className="error">{errors.name}</span>}
      {/* Otros campos */}
      <button type="submit">Create Market</button>    </form>  )}

Patrón Error Boundary

typescript
interface ErrorBoundaryState {  hasError: boolean  error: Error | null}
export class ErrorBoundary extends React.Component<  { children: React.ReactNode },  ErrorBoundaryState> {  state: ErrorBoundaryState = {    hasError: false,    error: null  }
  static getDerivedStateFromError(error: Error): ErrorBoundaryState {    return { hasError: true, error }  }
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {    console.error('Error boundary caught:', error, errorInfo)  }
  render() {    if (this.state.hasError) {      return (        <div className="error-fallback">          <h2>Something went wrong</h2>          <p>{this.state.error?.message}</p>          <button onClick={() => this.setState({ hasError: false })}>            Try again          </button>        </div>      )    }
    return this.props.children  }}
// Uso<ErrorBoundary>  <App /></ErrorBoundary>

Patrones de Animación

Animaciones con Framer Motion

typescript
import { motion, AnimatePresence } from 'framer-motion'
// PASS: Animaciones de listaexport function AnimatedMarketList({ markets }: { markets: Market[] }) {  return (    <AnimatePresence>      {markets.map(market => (        <motion.div          key={market.id}          initial={{ opacity: 0, y: 20 }}          animate={{ opacity: 1, y: 0 }}          exit={{ opacity: 0, y: -20 }}          transition={{ duration: 0.3 }}        >          <MarketCard market={market} />        </motion.div>      ))}    </AnimatePresence>  )}
// PASS: Animaciones de modalexport function Modal({ isOpen, onClose, children }: ModalProps) {  return (    <AnimatePresence>      {isOpen && (        <>          <motion.div            className="modal-overlay"            initial={{ opacity: 0 }}            animate={{ opacity: 1 }}            exit={{ opacity: 0 }}            onClick={onClose}          />          <motion.div            className="modal-content"            initial={{ opacity: 0, scale: 0.9, y: 20 }}            animate={{ opacity: 1, scale: 1, y: 0 }}            exit={{ opacity: 0, scale: 0.9, y: 20 }}          >            {children}          </motion.div>        </>      )}    </AnimatePresence>  )}

Patrones de Accesibilidad

Navegación por Teclado

typescript
export function Dropdown({ options, onSelect }: DropdownProps) {  const [isOpen, setIsOpen] = useState(false)  const [activeIndex, setActiveIndex] = useState(0)
  const handleKeyDown = (e: React.KeyboardEvent) => {    switch (e.key) {      case 'ArrowDown':        e.preventDefault()        setActiveIndex(i => Math.min(i + 1, options.length - 1))        break      case 'ArrowUp':        e.preventDefault()        setActiveIndex(i => Math.max(i - 1, 0))        break      case 'Enter':        e.preventDefault()        onSelect(options[activeIndex])        setIsOpen(false)        break      case 'Escape':        setIsOpen(false)        break    }  }
  return (    <div      role="combobox"      aria-expanded={isOpen}      aria-haspopup="listbox"      onKeyDown={handleKeyDown}    >      {/* Implementación del Dropdown */}    </div>  )}

Gestión de Foco

typescript
export function Modal({ isOpen, onClose, children }: ModalProps) {  const modalRef = useRef<HTMLDivElement>(null)  const previousFocusRef = useRef<HTMLElement | null>(null)
  useEffect(() => {    if (isOpen) {      // Guardar elemento actualmente enfocado      previousFocusRef.current = document.activeElement as HTMLElement
      // Enfocar modal      modalRef.current?.focus()    } else {      // Restaurar foco al cerrar      previousFocusRef.current?.focus()    }  }, [isOpen])
  return isOpen ? (    <div      ref={modalRef}      role="dialog"      aria-modal="true"      tabIndex={-1}      onKeyDown={e => e.key === 'Escape' && onClose()}    >      {children}    </div>  ) : null}

Recuerda: Los patrones modernos de frontend permiten interfaces de usuario mantenibles y de alto rendimiento. Elige los patrones que se ajusten a la complejidad de tu proyecto.

Source and attribution

Source:affaan-m/ECCindocs/es/skills/frontend-patternsat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal