Tools
Tools: Patrones de diseño de componentes de React: una guía completa
2026-02-23
0 views
admin
Compound Components ## Concepto ## Caso de uso ## Ejemplo con TypeScript ## TabsContext.tsx ## TabsRoot.tsx ## TabsList.tsx ## TabsTrigger.tsx ## TabsContent.tsx ## index.tsx ## Uso del componente ## Librerías que usan este patrón ## Radix UI ## Headless UI ## Chakra UI ## Reach UI ## Ventajas del patrón ## Desventajas a considerar ## Slot Pattern ## Concepto ## Caso de uso ## Ejemplo con TypeScript ## CardTypes.ts ## Card.tsx ## ProductCard.tsx ## DashboardLayout.tsx ## App.tsx ## Librerías que usan este patrón ## Material-UI (MUI) ## Next.js App Router ## Shadcn/ui ## Ant Design ## Ventajas del patrón ## Desventajas a considerar ## Higher-Order Components (HOC) ## Contexto histórico ## Concepto ## Caso de uso ## Ejemplo con TypeScript ## withAuth.tsx ## withErrorBoundary.tsx ## withLoading.tsx ## withAnalytics.tsx ## compose.ts ## Usage Examples ## Librerías que usan o usaban este patrón ## React Redux ## React Router ## Apollo Client ## Material-UI ## Ventajas del patrón ## Desventajas a considerar ## Contexto histórico ## Concepto ## Caso de uso ## Ejemplo con TypeScript ## withAuth.tsx ## withErrorBoundary.tsx ## withLoading.tsx ## withAnalytics.tsx ## compose.ts ## Usage Examples ## Librerías que usan o usaban este patrón ## React Redux ## React Router ## Apollo Client ## Material-UI ## Ventajas del patrón ## Desventajas a considerar ## Render Props ## Contexto histórico ## Concepto ## Caso de uso ## Ejemplo con TypeScript ## MouseTracker.tsx ## DataFetcher.tsx ## Toggle.tsx ## FormField.tsx ## Usage Examples ## Librerías que usan o usaban este patrón ## Formik ## Downshift ## React Motion ## React Measure ## Ventajas del patrón ## Desventajas a considerar ## Controlled vs Uncontrolled Components ## Concepto ## Caso de uso ## Componentes controlados ## Componentes no controlados ## Ejemplo con TypeScript ## ControlledForm.tsx ## UncontrolledForm.tsx ## HybridInput.tsx ## FileUpload.tsx ## Librerías que usan estos enfoques ## React Hook Form (Enfoque no controlado) ## Formik (Enfoque controlado) ## Radix UI (Soporta ambos) ## Ventajas de componentes controlados ## Desventajas de componentes controlados ## Ventajas de componentes no controlados ## Desventajas de componentes no controlados ## Props Getters Pattern ## Concepto ## Caso de uso ## Ejemplo con TypeScript ## utils.ts ## useDropdown.tsx ## useTooltip.tsx ## useAccordion.tsx ## App.tsx ## Librerías que usan este patrón ## Downshift ## React Aria (Adobe) ## React Table v7 (Histórico) ## Ventajas del patrón ## Desventajas a considerar ## Headless Component Pattern ## Concepto ## Caso de uso ## Ejemplo con TypeScript ## types.ts ## useSelect.ts ## SimpleSelect.tsx ## ComboboxSelect.tsx ## App.tsx ## Librerías que usan este patrón ## Headless UI ## TanStack Table ## Downshift ## React Aria ## Radix UI ## Ventajas del patrón ## Desventajas a considerar En este post exploraremos algunos de los patrones más utilizados al momento de construir componentes en React y cómo pueden ayudarte a desarrollar aplicaciones más escalables, mantenibles y fáciles de entender. A lo largo de esta guía sobre Patrones de diseño de componentes de React: una guía completa, analizaremos diferentes enfoques que te permitirán estructurar mejor tu código, reutilizar lógica de manera eficiente y crear una mejorar estructura de tus componentes. El patrón de Compound Components permite que varios componentes trabajen juntos de manera coordinada, compartiendo estado implícitamente a través de React Context. En lugar de tener un solo componente monolítico con muchas props, este patrón divide la funcionalidad en componentes más pequeños que se componen juntos. La característica principal es que estos componentes "compuestos" están diseñados para trabajar en conjunto y comparten estado interno sin necesidad de pasar props explícitamente entre ellos. El componente padre actúa como gestor del estado, mientras que los componentes hijos acceden a ese estado a través de Context. Este patrón proporciona una API declarativa y flexible que resulta intuitiva de usar, ya que la estructura del JSX refleja naturalmente la jerarquía visual del componente. El patrón Compound Components es ideal cuando necesitas crear componentes que: Ejemplos típicos incluyen: Tabs (Pestañas): Un componente de tabs tiene triggers (botones) y panels (contenido), todos deben conocer qué pestaña está activa. Accordions (Acordeones): Los headers y contenidos deben coordinarse para mostrar/ocultar secciones. Select/Dropdown: El trigger, menú e items deben trabajar juntos para manejar la selección. Modals/Dialogs: El trigger, overlay, contenido y botones de cierre necesitan coordinar el estado de apertura. Vamos a construir un componente Tabs usando el patrón Compound Components. Dividiremos el ejemplo en archivos separados para una mejor organización: Radix UI es una de las implementaciones más populares del patrón Compound Components. Esta librería proporciona componentes primitivos sin estilos que priorizan la accesibilidad y la flexibilidad. Ejemplo real de Radix UI: Por qué Radix UI usa este patrón: Desarrollado por Tailwind Labs, Headless UI utiliza Compound Components para proporcionar componentes completamente funcionales sin estilos predefinidos. Ejemplo con Headless UI: Ventajas de este enfoque: Chakra UI utiliza Compound Components para muchos de sus componentes complejos, combinándolo con un sistema de diseño completo. Por qué funciona bien en Chakra: Reach UI fue uno de los pioneros en popularizar este patrón para componentes accesibles en React. API expresiva y clara: La estructura del JSX comunica claramente la relación entre componentes. Flexibilidad en la composición: Los usuarios pueden reordenar, anidar o condicionar los componentes según necesiten. Encapsulación del estado: El estado se mantiene privado dentro del componente padre, sin contaminación del scope global. Separación de responsabilidades: Cada sub-componente tiene una única responsabilidad clara. Facilidad de testing: Los componentes individuales pueden testearse de manera aislada. Complejidad de implementación: Requiere más código inicial que un componente simple con props. Curva de aprendizaje: Los desarrolladores necesitan entender Context API y el patrón específico. Posible overhead de performance: El Context puede causar re-renders si no se optimiza correctamente. Dependencia de estructura: Los componentes deben usarse en una estructura específica, lo que puede resultar confuso si no está bien documentado. El patrón Slot permite crear componentes con áreas específicas personalizables donde se puede inyectar contenido arbitrario. A diferencia de simplemente usar children, los slots proporcionan múltiples puntos de inyección nombrados, cada uno con un propósito específico dentro de la estructura del componente. Este patrón trata los diferentes elementos de un componente como "ranuras" (slots) que pueden ser rellenadas con contenido personalizado. Cada slot representa una sección específica del componente con una ubicación y propósito definido, permitiendo una personalización estructural sin comprometer la lógica interna del componente. La implementación más común del patrón Slot en React se realiza a través de props específicas para cada sección del componente. Por ejemplo, un componente Card puede tener slots para header, footer, media y actions, cada uno representando una sección distinta de la tarjeta. El patrón Slot es especialmente útil cuando necesitas: Layouts flexibles: Componentes que requieren múltiples áreas personalizables con posiciones específicas. Por ejemplo, un layout de dashboard con header, sidebar, main content y footer, donde cada sección puede contener diferentes componentes según la página. Componentes con estructura fija pero contenido variable: Cuando tienes un componente con una estructura visual consistente pero el contenido de cada sección varía. Las cards son un ejemplo perfecto, ya que siempre tienen las mismas secciones (header, body, footer) pero el contenido cambia. Personalización sin perder consistencia: Cuando quieres permitir personalización manteniendo una estructura visual coherente en toda la aplicación. Los slots aseguran que ciertas áreas permanezcan en posiciones específicas. Composición de componentes complejos: Para construir componentes que combinan múltiples elementos en posiciones específicas, como modals con header, body y footer, o navigation bars con logo, menú y acciones de usuario. Ejemplos específicos incluyen: Cards de productos: Con secciones para imagen, título, descripción, precio y acciones de compra. Dialogs/Modals: Con header (título y botón cerrar), content (cuerpo del modal) y actions (botones de confirmación/cancelación). Navigation Bars: Con slots para logo, menú principal, búsqueda y acciones de usuario. Page Layouts: Con slots para header, sidebar, main content, y footer. Vamos a construir un componente Card flexible usando el patrón Slot. Separamos la implementación en archivos para mayor claridad: Material-UI ha estandarizado el uso de slots a través de las props slots y slotProps en todos sus componentes. Este enfoque permite una personalización granular de cada parte del componente. Ejemplo con el componente DatePicker: Ejemplo con el componente Card: Por qué MUI usa este patrón: Next.js implementa slots de manera nativa en su sistema de routing a través de layouts y parallel routes. El patrón se utiliza extensivamente para la composición de páginas. Ejemplo básico de layout: Ejemplo con parallel routes (slots nombrados): Ventajas del enfoque de Next.js: Shadcn/ui utiliza el patrón Slot de manera implícita en sus componentes, aprovechando la composición natural de React junto con Radix UI primitives. Por qué funciona en Shadcn/ui: Ant Design utiliza una variación del patrón Slot a través de props específicas y componentes separados para cada sección. Características del enfoque de Ant Design: Flexibilidad estructural máxima: Los slots permiten personalizar completamente cada sección del componente sin modificar su implementación interna. Puedes cambiar el contenido de cualquier slot sin afectar a los demás. API clara y autodocumentada: Los nombres de las props dejan claro qué contenido va en cada sección. Al ver header, footer, o actions, inmediatamente entiendes dónde se renderizará ese contenido. No requiere Context API: A diferencia del patrón Compound Components, los slots no necesitan compartir estado implícitamente. Cada slot recibe su contenido directamente como prop, haciendo el flujo de datos más explícito y fácil de seguir. Fácil de entender para desarrolladores: El patrón es intuitivo y no requiere conocer APIs especiales o patrones complejos. Si sabes usar props en React, sabes usar slots. Composición explícita: La estructura del JSX muestra claramente qué contenido va en cada slot, sin magia ni abstracciones ocultas. Esto facilita el debugging y el mantenimiento. Type safety natural: TypeScript puede inferir y validar fácilmente los tipos de cada slot, proporcionando autocompletado y detección de errores en tiempo de desarrollo. Sin overhead de performance: No hay Context API, no hay re-renders innecesarios. Los slots son simplemente props que se renderizan en posiciones específicas. Puede llevar a prop explosion: Componentes con muchos slots pueden terminar con una lista muy larga de props, lo que puede hacer el código verboso y difícil de mantener. Verbosidad en algunos casos: Para componentes simples, usar múltiples props para slots puede ser más código del necesario comparado con simplemente usar children. Menos "mágico" que otros patrones: No hay coordinación automática entre slots como en Compound Components. Si necesitas que los slots compartan estado, tendrás que manejarlo explícitamente. Requiere planificación cuidadosa: Necesitas decidir desde el principio qué secciones serán slots. Añadir nuevos slots después puede romper la compatibilidad hacia atrás si no se hace como props opcionales. No hay jerarquía implícita: A diferencia de Compound Components donde los sub-componentes conocen su contexto, los slots son completamente independientes. Esto puede ser una ventaja o desventaja según el caso de uso. Puede confundir cuándo usar cada slot: Sin documentación clara, los usuarios del componente pueden no saber qué slot usar para su contenido o cuál es la diferencia entre slots similares. Los Higher-Order Components fueron uno de los patrones más utilizados en React antes de la introducción de Hooks en 2019. Este patrón surgió como la solución oficial recomendada por el equipo de React para reemplazar los mixins, que fueron considerados problemáticos y eventualmente deprecados. Durante varios años, los HOCs dominaron el ecosistema de React y fueron adoptados masivamente por librerías populares como Redux (con connect()), React Router (con withRouter), Apollo Client (con graphql()), y Material-UI (con withStyles y withTheme). Sin embargo, con la llegada de React Hooks, este patrón ha sido en gran medida reemplazado por custom hooks, que ofrecen una API más simple y menos propensa a errores. Hoy en día, los HOCs son considerados un patrón legacy. Aunque siguen siendo válidos y funcionan perfectamente en React moderno, las librerías principales del ecosistema han migrado hacia hooks como su API recomendada. Por ejemplo, React Router removió completamente withRouter en su versión 6, Apollo Client deprecó sus HOCs en favor de hooks como useQuery y useMutation, y Redux recomienda usar useSelector y useDispatch en lugar de connect(). Un Higher-Order Component es una función que recibe un componente como argumento y devuelve un nuevo componente con funcionalidad adicional. Es importante entender que un HOC no modifica el componente original, sino que lo compone envolviéndolo en un componente contenedor. La firma básica de un HOC es: El HOC envuelve el componente original y puede: Es una aplicación del patrón de composición funcional aplicado a componentes React. El nombre viene de las funciones de orden superior (higher-order functions) en programación funcional, que son funciones que toman otras funciones como argumentos o las devuelven como resultado. Aunque los HOCs son legacy, todavía hay escenarios donde pueden ser útiles o necesarios: Código legacy existente: Si tienes una aplicación grande construida con HOCs, puede no ser práctico refactorizar todo a hooks inmediatamente. Los HOCs seguirán funcionando perfectamente. Componentes de clase que no se pueden migrar: Si por alguna razón necesitas mantener componentes de clase y no puedes usar hooks directamente, los HOCs siguen siendo una forma válida de compartir lógica. Cross-cutting concerns transversales: Para funcionalidad que debe aplicarse consistentemente a múltiples componentes, como autenticación, logging, manejo de errores, o analytics. Interoperabilidad con librerías antiguas: Cuando trabajas con librerías que todavía usan HOCs en su API pública. Error Boundaries: Los Error Boundaries todavía requieren componentes de clase en React, por lo que un HOC puede ser una forma útil de agregar error handling a componentes funcionales. Casos específicos donde los HOCs fueron comunes (antes de hooks): Autenticación y autorización: Envolver componentes para verificar si el usuario está autenticado o tiene permisos específicos. Conexión a stores: Como connect() de Redux para conectar componentes al estado global. Routing: Como withRouter para inyectar props de navegación. Theming: Para inyectar el tema actual en componentes. Tracking y analytics: Para registrar automáticamente eventos cuando los componentes se montan o se interactúa con ellos. Vamos a construir varios HOCs útiles con TypeScript. Separamos cada HOC en su propio archivo: React Redux introdujo el HOC connect() como su API principal durante muchos años. Aunque todavía es compatible en la versión 8.x, el equipo recomienda usar hooks como el enfoque predeterminado. Ejemplo legacy con connect: Enfoque moderno con hooks: Estado actual: connect() sigue siendo soportado pero se recomienda usar hooks. El equipo de Redux declaró que los hooks son ahora la forma recomendada de interactuar con Redux. React Router proporcionó withRouter para inyectar props de navegación en componentes. Este HOC fue completamente removido en React Router v6. Ejemplo con withRouter (React Router v5): Enfoque moderno (React Router v6): Estado actual: withRouter fue completamente removido en React Router v6. La librería ahora usa exclusivamente hooks. Apollo Client tenía el HOC graphql() como su API principal para ejecutar queries y mutations. Este fue deprecado en favor de hooks. Ejemplo legacy con graphql HOC: Enfoque moderno con hooks: Estado actual: Los HOCs de Apollo fueron deprecados en Apollo Client 3 y movidos a paquetes separados. Se recomienda usar hooks exclusivamente. Material-UI (ahora MUI) proporcionaba withStyles y withTheme para inyectar estilos y temas. Estos han sido reemplazados por hooks y el sistema sx. Ejemplo legacy con withStyles: Enfoque moderno con hooks y sx: Estado actual: withStyles y withTheme todavía funcionan pero se consideran legacy. MUI recomienda usar el sistema sx o styled components. Reutilización de lógica cross-cutting: Permite extraer lógica común que se aplica a múltiples componentes sin duplicar código. Composición funcional: Los HOCs se pueden componer fácilmente usando funciones como compose(), permitiendo aplicar múltiples capas de funcionalidad. No modifica el componente original: El componente base permanece puro y sin modificar, lo que facilita el testing y el razonamiento sobre el código. Separación de concerns: Mantiene la lógica de infraestructura (auth, logging, etc.) separada de la lógica de presentación. Compatible con componentes de clase: A diferencia de hooks, los HOCs funcionan tanto con componentes funcionales como de clase. Wrapper hell: Aplicar múltiples HOCs puede resultar en un árbol de componentes profundamente anidado que es difícil de debuggear y visualizar en React DevTools. Props collision: Si múltiples HOCs intentan inyectar props con el mismo nombre, pueden sobrescribirse mutuamente causando bugs difíciles de rastrear. Nombres genéricos en DevTools: Los componentes envueltos aparecen con nombres como WithAuth(WithLoading(Component)) que pueden ser confusos. Props forwarding manual: Es necesario manualmente pasar todas las props que no son usadas por el HOC al componente envuelto, lo que puede ser propenso a errores. Imposibilidad de usar hooks dentro del HOC tradicional: Los HOCs tradicionales basados en clases no pueden usar hooks de React, aunque los HOCs modernos implementados como funciones sí pueden. Refs no se pasan automáticamente: Es necesario usar forwardRef explícitamente si quieres que las refs funcionen correctamente. Curva de aprendizaje: Para desarrolladores nuevos, entender el concepto de HOCs y cómo funcionan puede ser confuso inicialmente. Overhead de performance mínimo: Cada HOC agrega un componente adicional en el árbol, lo que puede tener un pequeño impacto en performance si se usan muchos HOCs anidados.# Higher-Order Components (HOC) Los Higher-Order Components fueron uno de los patrones más utilizados en React antes de la introducción de Hooks en 2019. Este patrón surgió como la solución oficial recomendada por el equipo de React para reemplazar los mixins, que fueron considerados problemáticos y eventualmente deprecados. Durante varios años, los HOCs dominaron el ecosistema de React y fueron adoptados masivamente por librerías populares como Redux (con connect()), React Router (con withRouter), Apollo Client (con graphql()), y Material-UI (con withStyles y withTheme). Sin embargo, con la llegada de React Hooks, este patrón ha sido en gran medida reemplazado por custom hooks, que ofrecen una API más simple y menos propensa a errores. Hoy en día, los HOCs son considerados un patrón legacy. Aunque siguen siendo válidos y funcionan perfectamente en React moderno, las librerías principales del ecosistema han migrado hacia hooks como su API recomendada. Por ejemplo, React Router removió completamente withRouter en su versión 6, Apollo Client deprecó sus HOCs en favor de hooks como useQuery y useMutation, y Redux recomienda usar useSelector y useDispatch en lugar de connect(). Un Higher-Order Component es una función que recibe un componente como argumento y devuelve un nuevo componente con funcionalidad adicional. Es importante entender que un HOC no modifica el componente original, sino que lo compone envolviéndolo en un componente contenedor. La firma básica de un HOC es: El HOC envuelve el componente original y puede: Es una aplicación del patrón de composición funcional aplicado a componentes React. El nombre viene de las funciones de orden superior (higher-order functions) en programación funcional, que son funciones que toman otras funciones como argumentos o las devuelven como resultado. Aunque los HOCs son legacy, todavía hay escenarios donde pueden ser útiles o necesarios: Código legacy existente: Si tienes una aplicación grande construida con HOCs, puede no ser práctico refactorizar todo a hooks inmediatamente. Los HOCs seguirán funcionando perfectamente. Componentes de clase que no se pueden migrar: Si por alguna razón necesitas mantener componentes de clase y no puedes usar hooks directamente, los HOCs siguen siendo una forma válida de compartir lógica. Cross-cutting concerns transversales: Para funcionalidad que debe aplicarse consistentemente a múltiples componentes, como autenticación, logging, manejo de errores, o analytics. Interoperabilidad con librerías antiguas: Cuando trabajas con librerías que todavía usan HOCs en su API pública. Error Boundaries: Los Error Boundaries todavía requieren componentes de clase en React, por lo que un HOC puede ser una forma útil de agregar error handling a componentes funcionales. Casos específicos donde los HOCs fueron comunes (antes de hooks): Autenticación y autorización: Envolver componentes para verificar si el usuario está autenticado o tiene permisos específicos. Conexión a stores: Como connect() de Redux para conectar componentes al estado global. Routing: Como withRouter para inyectar props de navegación. Theming: Para inyectar el tema actual en componentes. Tracking y analytics: Para registrar automáticamente eventos cuando los componentes se montan o se interactúa con ellos. Vamos a construir varios HOCs útiles con TypeScript. Separamos cada HOC en su propio archivo: React Redux introdujo el HOC connect() como su API principal durante muchos años. Aunque todavía es compatible en la versión 8.x, el equipo recomienda usar hooks como el enfoque predeterminado. Ejemplo legacy con connect: Enfoque moderno con hooks: Estado actual: connect() sigue siendo soportado pero se recomienda usar hooks. El equipo de Redux declaró que los hooks son ahora la forma recomendada de interactuar con Redux. React Router proporcionó withRouter para inyectar props de navegación en componentes. Este HOC fue completamente removido en React Router v6. Ejemplo con withRouter (React Router v5): Enfoque moderno (React Router v6): Estado actual: withRouter fue completamente removido en React Router v6. La librería ahora usa exclusivamente hooks. Apollo Client tenía el HOC graphql() como su API principal para ejecutar queries y mutations. Este fue deprecado en favor de hooks. Ejemplo legacy con graphql HOC: Enfoque moderno con hooks: Estado actual: Los HOCs de Apollo fueron deprecados en Apollo Client 3 y movidos a paquetes separados. Se recomienda usar hooks exclusivamente. Material-UI (ahora MUI) proporcionaba withStyles y withTheme para inyectar estilos y temas. Estos han sido reemplazados por hooks y el sistema sx. Ejemplo legacy con withStyles: Enfoque moderno con hooks y sx: Estado actual: withStyles y withTheme todavía funcionan pero se consideran legacy. MUI recomienda usar el sistema sx o styled components. Reutilización de lógica cross-cutting: Permite extraer lógica común que se aplica a múltiples componentes sin duplicar código. Composición funcional: Los HOCs se pueden componer fácilmente usando funciones como compose(), permitiendo aplicar múltiples capas de funcionalidad. No modifica el componente original: El componente base permanece puro y sin modificar, lo que facilita el testing y el razonamiento sobre el código. Separación de concerns: Mantiene la lógica de infraestructura (auth, logging, etc.) separada de la lógica de presentación. Compatible con componentes de clase: A diferencia de hooks, los HOCs funcionan tanto con componentes funcionales como de clase. Wrapper hell: Aplicar múltiples HOCs puede resultar en un árbol de componentes profundamente anidado que es difícil de debuggear y visualizar en React DevTools. Props collision: Si múltiples HOCs intentan inyectar props con el mismo nombre, pueden sobrescribirse mutuamente causando bugs difíciles de rastrear. Nombres genéricos en DevTools: Los componentes envueltos aparecen con nombres como WithAuth(WithLoading(Component)) que pueden ser confusos. Props forwarding manual: Es necesario manualmente pasar todas las props que no son usadas por el HOC al componente envuelto, lo que puede ser propenso a errores. Imposibilidad de usar hooks dentro del HOC tradicional: Los HOCs tradicionales basados en clases no pueden usar hooks de React, aunque los HOCs modernos implementados como funciones sí pueden. Refs no se pasan automáticamente: Es necesario usar forwardRef explícitamente si quieres que las refs funcionen correctamente. Curva de aprendizaje: Para desarrolladores nuevos, entender el concepto de HOCs y cómo funcionan puede ser confuso inicialmente. Overhead de performance mínimo: Cada HOC agrega un componente adicional en el árbol, lo que puede tener un pequeño impacto en performance si se usan muchos HOCs anidados. El patrón Render Props fue ampliamente adoptado en el ecosistema React entre 2017 y 2019 como solución para compartir lógica entre componentes. Surgió como una alternativa más flexible a los Higher-Order Components, resolviendo algunos de sus problemas más notorios como el wrapper hell y la colisión de props. Durante este período, Render Props se convirtió en el patrón recomendado por la documentación oficial de React para reutilización de código. Librerías populares como React Motion, Downshift, Formik, React Router (en algunas versiones), y Apollo Client adoptaron este patrón como su API principal, validando su efectividad para compartir comportamiento stateful. Sin embargo, con la introducción de React Hooks en febrero de 2019, el patrón Render Props rápidamente comenzó a perder relevancia. Los hooks proporcionaron una forma más simple y directa de compartir lógica sin necesidad de anidar componentes o usar callbacks complejos. La mayoría de las librerías que usaban Render Props migraron sus APIs a hooks, manteniendo el soporte para render props principalmente por compatibilidad hacia atrás. Hoy en día, Render Props es considerado un patrón legacy. Aunque técnicamente sigue siendo válido y funciona perfectamente, las librerías principales han deprecado o están en proceso de remover sus implementaciones de render props en favor de hooks. Por ejemplo, Downshift planea remover completamente su componente con render props una vez que sus hooks maduren, y Formik recomienda usar su hook useFormik sobre el componente <Formik> con render props. El patrón Render Props consiste en un componente que recibe una función como prop, usualmente llamada render o children, y la utiliza para determinar qué renderizar. Esta función recibe como argumento el estado y los métodos del componente, permitiendo que el consumidor controle completamente la presentación mientras el componente maneja toda la lógica. La característica distintiva de este patrón es que invierte el control del renderizado. En lugar de que el componente decida cómo renderizar su contenido, delega esa responsabilidad al consumidor a través de una función. El componente solo se encarga de la lógica stateful (como tracking de mouse, data fetching, o manejo de formularios) y expone su estado a través de los argumentos de la función render. Las dos formas más comunes de implementar render props son: A través de una prop específica llamada render: A través de la prop children como función: Ambas formas son funcionalmente equivalentes, aunque usar children como función suele ser más idiomático en React. Aunque Render Props es un patrón legacy, aún existen algunos casos donde todavía se usa o puede ser útil: Código legacy que no se puede migrar inmediatamente: Si tienes una aplicación grande construida con componentes que usan render props, puede no ser práctico refactorizar todo de inmediato. El código legacy seguirá funcionando perfectamente. Librerías que aún lo soportan: Algunas librerías mantienen soporte para render props por compatibilidad hacia atrás. Si estás usando una versión antigua de una librería, es posible que render props sea la única opción disponible. Casos donde necesitas múltiples render props: Aunque poco común, hay situaciones donde un componente puede necesitar múltiples funciones de renderizado para diferentes secciones, algo que es más natural con props nombradas que con hooks. Encapsulación de lógica sin hooks: Si por alguna razón no puedes usar hooks (por ejemplo, en una base de código muy antigua que aún usa componentes de clase), render props sigue siendo una opción viable. Los casos de uso históricos donde Render Props fue común incluyen: Mouse tracking y gesture handling: Compartir posición del mouse, velocidad, dirección, etc. Data fetching con estados: Loading, error y success states compartidos entre componentes. Form state management: Validación, valores de campos, estado de submission. Animaciones: Valores de animación, progreso, estados de transición. Toggle y modal state: Estado on/off compartido entre trigger y contenido. Virtualization: Scroll position, visible items, y mediciones de elementos. Vamos a construir varios componentes usando el patrón Render Props con TypeScript: Formik fue una de las librerías más populares para manejo de formularios en React, y usó Render Props como su API principal durante varios años. Aunque todavía soporta render props, ahora recomienda usar el hook useFormik. Ejemplo legacy con render props: Enfoque moderno con hooks: Estado actual: Formik todavía soporta render props pero la documentación recomienda usar useFormik como enfoque preferido. Algunos render props específicos como el de <Field> han sido deprecados con advertencias en consola. El proyecto no está activamente mantenido según su historial de commits. Downshift de Paypal fue pionero en el uso de Render Props para crear componentes accesibles de select, combobox y autocomplete. El componente original con render props será eventualmente removido. Ejemplo legacy con render props: Enfoque moderno con hooks: Estado actual: El componente Downshift original con render props todavía funciona pero está planificado para ser removido completamente. La documentación oficial afirma: "El componente Downshift será removido completamente una vez que los hooks maduren". Se recomienda fuertemente usar useSelect, useCombobox y useMultipleSelection para todos los casos de uso nuevos. React Motion usó render props para proporcionar valores de animación a los componentes. Aunque la librería no ha sido actualizada en años, fue muy influyente en el ecosistema. Ejemplo con React Motion: Estado actual: React Motion no ha sido actualizado desde 2018 y es considerado abandonado. Librerías modernas como Framer Motion usan hooks exclusivamente en su lugar. React Measure usó render props para proporcionar mediciones de elementos DOM. Estado actual: React Measure está deprecado. Los casos de uso modernos usan hooks como useResizeObserver o useMeasure de librerías como react-use. Flexibilidad en el renderizado: El consumidor tiene control total sobre cómo se renderiza el contenido. Puede decidir la estructura, estilos y composición sin estar limitado por las decisiones del componente. Separación de lógica y presentación: La lógica stateful está encapsulada en el componente, mientras que la presentación es responsabilidad del consumidor. Esta separación hace el código más modular y testeable. Código más explícito que HOCs: A diferencia de los HOCs, donde las props "aparecen mágicamente", con render props el flujo de datos es completamente visible. Puedes ver exactamente qué datos están disponibles y cómo se usan. No hay colisión de props: Cada render prop recibe sus argumentos explícitamente, por lo que no hay riesgo de que múltiples render props sobrescriban las mismas props como puede ocurrir con HOCs. Composición natural: Puedes componer múltiples componentes con render props de manera natural sin crear estructuras profundamente anidadas en el árbol de React DevTools. Type safety con TypeScript: TypeScript puede inferir y validar fácilmente los tipos de los argumentos de la función render, proporcionando excelente autocompletado y detección de errores. Callback hell: Cuando se componen múltiples render props, el código puede volverse difícil de leer debido al anidamiento de funciones. Esto es a menudo llamado "pyramid of doom". Sintaxis más verbosa que hooks: Comparado con hooks, render props requiere más código para lograr el mismo resultado. Los hooks proporcionan una API más limpia y directa. Problemas de performance si no se optimiza: Si la función render se crea inline en cada render, puede causar re-renders innecesarios. Esto requiere usar useCallback o memoization manual para optimizar. No se puede usar condicionalemente: A diferencia de los hooks, un componente con render props no puede usarse condicionalmente dentro de otro componente, ya que siempre debe estar en el JSX. Más difícil de debuggear: El stack trace puede ser confuso con múltiples niveles de funciones render anidadas, haciendo más difícil identificar dónde ocurrió un error. Hooks lo han reemplazado casi completamente: Este es el problema más significativo: los hooks proporcionan una solución superior para la mayoría de los casos de uso que render props solía resolver, haciendo que este patrón sea innecesario en código nuevo. Dificulta la extracción de componentes: Cuando tienes lógica compleja dentro de una función render, extraerla a un componente separado requiere más refactoring que con hooks. La distinción entre componentes controlados y no controlados es un concepto fundamental en React que se refiere a cómo se maneja el estado de los elementos de formulario. Un componente controlado es aquel cuyo valor es gestionado completamente por React a través del estado del componente. En este enfoque, React actúa como la "fuente única de verdad" (single source of truth). Cada cambio en el input dispara un evento que actualiza el estado de React, y este estado actualizado se refleja de vuelta en el valor del input. Esto crea un flujo de datos unidireccional y predecible. Un componente no controlado, por el contrario, mantiene su propio estado interno en el DOM, igual que los elementos HTML tradicionales. React no gestiona el valor del input directamente; en su lugar, accedemos al valor cuando lo necesitamos mediante referencias (refs) al nodo del DOM. El DOM actúa como la fuente de verdad, no el estado de React. La diferencia fundamental está en dónde vive el estado: En componentes controlados, usamos las props value y onChange: En componentes no controlados, usamos defaultValue y ref: Ambos enfoques tienen sus escenarios ideales de aplicación: Validación en tiempo real: Cuando necesitas validar cada tecla que el usuario presiona. Por ejemplo, verificar instantáneamente si un nombre de usuario está disponible, o validar el formato de un número de tarjeta de crédito mientras se escribe. Formateo dinámico de inputs: Para formatear automáticamente valores mientras el usuario escribe. Ejemplos incluyen formateo de números de teléfono (agregando guiones automáticamente), fechas, o moneda. Habilitar/deshabilitar elementos condicionalmente: Cuando un botón debe estar deshabilitado hasta que ciertos campos sean válidos, o cuando campos deben aparecer/desaparecer basándose en valores de otros campos. Formularios con dependencias entre campos: Cuando el valor o validación de un campo depende del valor de otro. Por ejemplo, un campo de "confirmar contraseña" que debe coincidir con el campo de "contraseña", o campos de dirección que cambian según el país seleccionado. Múltiples fuentes de actualización: Cuando el valor del input puede ser modificado desde múltiples lugares en tu aplicación, no solo por el usuario escribiendo. Formularios simples sin validación compleja: Cuando solo necesitas los valores al momento del submit y no requieres interacción durante la entrada. Mejor performance en formularios grandes: Con muchos campos (50+), donde el re-render en cada tecla puede causar problemas de performance. Los componentes no controlados no causan re-renders en cada cambio. Integración con código no-React: Cuando necesitas integrar React con librerías JavaScript tradicionales o código legacy que trabaja directamente con el DOM. File inputs: Los inputs de tipo file son siempre no controlados por naturaleza, ya que su valor solo puede ser establecido por el usuario por razones de seguridad. Formularios donde solo importa el valor final: Como búsquedas simples, filtros básicos, o cualquier caso donde no necesitas saber el valor hasta que el usuario termine de escribir o envíe el formulario. Vamos a construir ejemplos de ambos enfoques, separados por archivos: React Hook Form es una de las librerías de formularios más populares y está construida sobre el principio de componentes no controlados. Utiliza refs para acceder a los valores de los inputs, lo que resulta en mejor performance al reducir la cantidad de re-renders. Integración con componentes controlados mediante Controller: Por qué React Hook Form prefiere componentes no controlados: Estado actual: React Hook Form es la librería de formularios más popular en 2026 según npm downloads. La documentación oficial recomienda usar componentes no controlados siempre que sea posible, y solo usar Controller cuando se integra con librerías de UI que requieren componentes controlados como Material-UI, Ant Design, o React Select. Formik fue durante años la librería de formularios más popular y está construida completamente sobre componentes controlados. Aunque el proyecto no está activamente mantenido desde mediados de 2024, todavía es ampliamente usado en aplicaciones existentes. Con custom components: Por qué Formik usa componentes controlados: Estado actual: Formik no ha recibido actualizaciones mayores desde 2024. La comunidad recomienda migrar a React Hook Form o Final Form para proyectos nuevos. Sin embargo, Formik sigue siendo estable y funcional en aplicaciones existentes. Radix UI proporciona componentes primitivos que pueden funcionar tanto como controlados como no controlados, dando flexibilidad al desarrollador. Ejemplo con componentes no controlados (defaultValue): Ejemplo con componentes controlados (value): Por qué Radix UI soporta ambos: Control total del estado: Tienes acceso completo al valor actual en cualquier momento, lo que facilita implementar lógica compleja que depende del estado actual del formulario. Validación instantánea y dinámica: Puedes validar y mostrar errores en tiempo real mientras el usuario escribe, proporcionando feedback inmediato. Habilitación condicional de campos: Es sencillo habilitar, deshabilitar o mostrar campos basándose en los valores de otros campos. Sincronización entre múltiples fuentes: Si un valor puede cambiar desde diferentes partes de tu aplicación, los componentes controlados aseguran que la UI siempre refleje el estado actual. Formateo automático de valores: Puedes transformar el valor mientras el usuario escribe, como formatear números de teléfono, moneda, o convertir a mayúsculas/minúsculas. Testing más sencillo: El estado está en React, por lo que es más fácil de testear sin necesidad de interactuar con el DOM directamente. Reseteo simple: Puedes resetear todo el formulario simplemente actualizando el estado a sus valores iniciales. Performance en formularios grandes: Cada cambio causa un re-render del componente, lo que puede ser problemático en formularios con 50+ campos o lógica compleja. Más código boilerplate: Necesitas declarar estado y handlers para cada campo, resultando en más líneas de código. Complejidad incrementada: El código puede volverse difícil de mantener en formularios muy grandes con muchas dependencias entre campos. Overhead de memoria: Mantener todo el estado en React consume más memoria que dejar que el DOM lo maneje. Pueden causar problemas con autocompletado del navegador: Algunos navegadores pueden tener problemas con el autocompletado cuando los valores son controlados por React. Mejor performance: No causan re-renders en cada cambio del input, lo que es especialmente beneficioso en formularios grandes. Menos código: No necesitas useState ni handlers onChange para cada campo, resultando en código más conciso. Integración natural con APIs del navegador: Funcionan bien con características nativas como autocompletado, validación HTML5, y gestores de contraseñas. Ideal para formularios simples: Cuando solo necesitas los valores al submit, es más directo que mantener estado. Menor uso de memoria: No duplicas el estado entre React y el DOM. Más rápido de implementar: Para formularios básicos, puedes construirlos más rápidamente sin configuración de estado. Control limitado durante la entrada: No puedes validar o reaccionar fácilmente a cambios mientras el usuario escribe. Dificulta validación instantánea: Implementar feedback en tiempo real requiere trabajo adicional con eventos y refs. No puedes resetear fácilmente: Resetear campos individuales requiere manipular refs directamente, lo cual es menos elegante. Testing más complejo: Necesitas interactuar con el DOM directamente en tus tests, lo que puede ser más verboso. Estado no accesible fácilmente: Si necesitas el valor actual para lógica condicional, debes acceder al DOM cada vez. Menos control sobre el flujo de datos: El estado vive fuera de React, lo que puede hacer el código menos predecible en aplicaciones complejas. Dificulta implementar funcionalidades avanzadas: Features como campos condicionales, validación interdependiente, o formateo dinámico son más difíciles de implementar. El patrón Props Getters es una técnica avanzada que permite a los componentes exponer funciones que devuelven props necesarias para elementos específicos. En lugar de pasar un objeto de props directamente, el componente proporciona funciones getter que, cuando se llaman, retornan un objeto con todas las props necesarias para que un elemento funcione correctamente. La característica distintiva de este patrón es que permite la composición de comportamientos. Cuando un usuario del componente quiere agregar sus propias props (como un onClick personalizado o un className específico), el prop getter puede combinar las props del usuario con las props internas del componente sin que una sobrescriba a la otra. Esto se logra mediante funciones de utilidad que componen múltiples event handlers y combinan props de manera inteligente. El patrón fue popularizado por Kent C. Dodds, especialmente a través de su trabajo en la librería Downshift, después de una sugerencia de Jared Forsyth. La motivación principal era resolver un problema fundamental: cómo permitir que los usuarios personalicen componentes sin exponer detalles de implementación ni romper la funcionalidad interna. Un prop getter típico acepta un objeto de props opcionales del usuario y retorna un nuevo objeto que incluye: La estructura básica de un prop getter se ve así: El patrón Props Getters es especialmente útil en escenarios específicos: Componentes de UI complejos con múltiples partes interactivas: Cuando tienes componentes como dropdowns, combobox, selects, o menús que tienen múltiples elementos (trigger, menú, items) que necesitan coordinarse. Cada elemento requiere props específicas para funcionalidad y accesibilidad, pero el usuario debe poder personalizar cada uno. Cuando necesitas permitir personalización sin exponer implementación: Si quieres que los usuarios puedan agregar sus propios event handlers, clases CSS, o atributos, pero sin que tengan que conocer los detalles internos de cómo funciona tu componente. Los prop getters encapsulan toda la lógica necesaria. Componentes que requieren accesibilidad compleja: Para componentes que deben cumplir con patrones ARIA específicos donde múltiples atributos deben coordinarse (como aria-controls, aria-expanded, aria-labelledby, aria-describedby). Los prop getters pueden garantizar que todos los atributos necesarios estén presentes y correctamente conectados. Composición de event handlers: Cuando múltiples event handlers deben ejecutarse para un mismo evento. Por ejemplo, un botón puede necesitar ejecutar tanto la lógica interna del componente como una función personalizada del usuario, ambas en el mismo onClick. Flexibilidad total en el markup: Cuando quieres dar a los usuarios control completo sobre qué elementos HTML renderizar y cómo estructurarlos, pero aún necesitas que funcionen correctamente con tu lógica de estado. Casos de uso específicos incluyen: Autocomplete/Combobox: Donde el input, el botón de toggle, el menú y los items necesitan múltiples props coordinadas. Modal/Dialog: Donde el trigger, overlay, contenido y botones de cierre necesitan props específicas. Tooltip: Donde el trigger y el tooltip necesitan coordinarse con eventos de hover y focus. Tabs: Donde los triggers y panels necesitan sincronizar su estado activo. Accordion: Donde los headers y contenidos necesitan gestionar su estado de expansión. Vamos a construir varios componentes usando el patrón Props Getters con TypeScript: Downshift es la librería que popularizó el patrón Props Getters y sigue siendo el ejemplo más representativo. Aunque el componente original con render props está planificado para ser removido, los hooks modernos de Downshift continúan usando este patrón activamente. Ejemplo con useSelect: Ejemplo con useCombobox: Por qué Downshift usa Props Getters: Los prop getters permiten a Downshift encapsular toda la complejidad de accesibilidad (ARIA attributes, keyboard navigation) y coordinación de estado sin forzar una estructura específica de markup. Los usuarios pueden: Estado actual: Downshift continúa activamente manteniendo y mejorando sus hooks con prop getters. Es la implementación de referencia del patrón y es ampliamente usada en producción en 2026. React Aria utiliza un enfoque ligeramente diferente pero conceptualmente similar a través de hooks que proporcionan props para elementos específicos. Aunque no se llaman explícitamente "prop getters", funcionan bajo el mismo principio. Ejemplo con hooks de React Aria: Por qué React Aria usa este enfoque: Adobe diseñó React Aria para proporcionar comportamiento y accesibilidad sin opiniones sobre estilos. Los hooks devuelven objetos de props que contienen todo lo necesario para que los elementos funcionen correctamente, incluyendo: Estado actual: React Aria es activamente mantenida y es la base de Adobe Spectrum. Es ampliamente adoptada en la industria para construir sistemas de diseño accesibles. React Table v7 usó el patrón Props Getters extensivamente. Aunque la versión 8 (TanStack Table) cambió su API, v7 sigue siendo un ejemplo histórico importante del patrón. Ejemplo con React Table v7: Por qué React Table v7 usó Props Getters: Permitía a los usuarios agregar sorting, filtering, y selection sin modificar el markup base. Los prop getters encapsulaban toda la lógica necesaria para estas funcionalidades mientras mantenían flexibilidad total en el rendering. Estado actual: React Table v7 ya no recibe actualizaciones. La versión 8 (TanStack Table) cambió a un modelo diferente, aunque conceptualmente similar, donde se accede a props a través de métodos de instancia en lugar de prop getters explícitos. Composición de comportamientos sin colisiones: Los prop getters permiten combinar event handlers del usuario con los internos del componente sin que uno sobrescriba al otro. Esto resuelve un problema fundamental que tienen los objetos de props estáticos. Encapsulación de complejidad: Toda la lógica de accesibilidad, event handling, y coordinación de estado está encapsulada dentro del getter. Los usuarios no necesitan conocer qué props son necesarias ni cómo funcionan internamente. Flexibilidad total en el markup: Los usuarios tienen control completo sobre qué elementos HTML renderizar, su estructura, y sus estilos. El componente no impone ninguna estructura DOM específica. API limpia y autodocumentada: Los nombres de los getters (como getToggleButtonProps, getMenuProps) comunican claramente para qué elemento están destinados. No hay ambigüedad sobre dónde aplicar cada conjunto de props. Facilita la accesibilidad: Los getters pueden garantizar que todos los atributos ARIA necesarios estén presentes y correctamente conectados, haciendo mucho más fácil crear componentes accesibles. Type safety con TypeScript: TypeScript puede inferir y validar los tipos de las props devueltas por cada getter, proporcionando autocompletado y detección de errores durante el desarrollo. Permite override granular: Los usuarios pueden sobrescribir selectivamente cualquier prop individual si necesitan comportamiento personalizado, manteniendo el resto de la funcionalidad intacta. Curva de aprendizaje: Para desarrolladores nuevos, entender el concepto de prop getters y cómo usarlos correctamente puede ser confuso inicialmente. La API requiere llamar funciones y spread de resultados, lo que no es tan intuitivo como simplemente pasar props. Verbosidad en el uso: Cada elemento requiere una llamada de función y spread operator, lo que resulta en más código comparado con componentes que simplemente reciben props directamente. Problemas de performance con memoization: Los prop getters crean nuevas funciones y objetos en cada render, lo que puede causar problemas cuando se usan con React.memo o useMemo. Cada llamada a un getter devuelve un nuevo objeto, rompiendo la igualdad referencial. Debugging más complejo: Cuando algo no funciona, puede ser difícil rastrear si el problema está en las props que pasas al getter, en las props que el getter devuelve, o en cómo estás aplicando esas props al elemento. Documentación extensiva necesaria: Los desarrolladores que usan tu componente necesitan documentación clara sobre qué getter usar para cada elemento, qué props opcionales pueden pasar, y cómo los getters componen comportamientos. No es intuitivo para todos: Algunos desarrolladores prefieren APIs más explícitas donde todas las props se pasan directamente. La "magia" de la composición automática puede ser confusa. Dificulta el análisis estático: Las herramientas de análisis estático y linters pueden tener dificultades para entender qué props están disponibles en el objeto devuelto por un getter, especialmente si no está perfectamente tipado. Overhead de abstracción: Para componentes muy simples, usar prop getters puede ser overkill. La abstracción adicional puede no valer la pena si el componente no tiene comportamiento complejo que encapsular. Puede ocultar detalles importantes: A veces, los usuarios necesitan saber exactamente qué está haciendo un componente internamente. Los prop getters, al encapsular tanto comportamiento, pueden hacer esto menos transparente. El patrón Headless Component representa una separación total entre la lógica de comportamiento y la presentación visual de un componente. Un componente headless proporciona toda la funcionalidad, manejo de estado, interacciones, accesibilidad y lógica de negocio, pero no incluye ningún markup HTML ni estilos CSS predeterminados. En esencia, un componente headless se encarga del "cerebro" de la operación mientras deja la "apariencia" completamente en manos del desarrollador que lo implementa. Este patrón típicamente se implementa a través de custom hooks que retornan estado y funciones helpers, o mediante componentes que utilizan render props o compound components sin aplicar estilos. La filosofía detrás de este patrón es que los aspectos más complejos de construir componentes UI no son los estilos, sino la lógica de interacción, el manejo de accesibilidad (ARIA, navegación por teclado, lectores de pantalla), el manejo de estado complejo, y el comportamiento cross-browser. Un componente headless maneja todas estas complejidades mientras te da libertad total sobre cómo se ve. Cuando visualizas este patrón, el componente headless aparece como una capa delgada que interfaz con las vistas JSX por un lado y se comunica con los modelos de datos subyacentes por el otro cuando es necesario. No prescribe ninguna estructura DOM específica ni aplica ningún estilo CSS. El patrón Headless Component es especialmente valioso en los siguientes escenarios: Construcción de design systems: Cuando estás construyendo un sistema de diseño desde cero y necesitas control total sobre el markup y los estilos. El componente headless proporciona toda la funcionalidad compleja mientras tu equipo de diseño puede implementar exactamente la visión visual que tienen. Múltiples implementaciones visuales: Cuando necesitas el mismo comportamiento con diferentes apariencias en distintas partes de tu aplicación o en múltiples productos. Por ejemplo, un dropdown puede verse completamente diferente en un panel administrativo versus una landing page de marketing, pero ambos comparten la misma lógica. Frameworks CSS específicos: Cuando tu proyecto utiliza un framework de utilidades como Tailwind CSS o un sistema de estilos particular, y necesitas componentes que se integren perfectamente sin tener que sobrescribir estilos predeterminados. Accesibilidad prioritaria: Cuando necesitas garantizar accesibilidad de nivel AAA pero tienes requerimientos de diseño muy específicos. Los componentes headless te dan accesibilidad completa lista para usar mientras mantienes control total del diseño. Librería de componentes personalizada: Si estás creando tu propia librería de componentes reutilizables, los componentes headless proporcionan la base perfecta ya que pueden ser estilizados para que se ajusten a cualquier diseño. Casos específicos incluyen: Tablas de datos complejas: Con sorting, filtering, paginación, selección de filas y virtualización. TanStack Table es el ejemplo perfecto de esto. Formularios avanzados: Con validación compleja, campos condicionales, arrays dinámicos y múltiples pasos. Componentes de selección: Autocomplete, combobox, multi-select con búsqueda, que son notoriamente difíciles de implementar correctamente. Interacciones gestuales: Drag and drop, swipe, long press, que requieren lógica compleja de detección de eventos. Componentes con estado complejo: Calendarios, date pickers, time pickers con zonas horarias y localización. Vamos a construir un componente de selección (Select/Combobox) headless con TypeScript, separando toda la lógica del markup: Desarrollada por Tailwind Labs, Headless UI es una colección de componentes completamente sin estilos y totalmente accesibles, diseñados para integrarse perfectamente con Tailwind CSS. Proporciona la funcionalidad y accesibilidad fundamental sobre la cual puedes construir tus propios componentes estilizados. Ejemplo real con Headless UI: Por qué Headless UI usa este patrón: TanStack Table (anteriormente React Table) es una librería headless para construir tablas y datagrids potentes. Proporciona toda la lógica para sorting, filtering, paginación, row selection y virtualización, sin imponer ningún markup o estilos. Ejemplo con TanStack Table: Ventajas de TanStack Table: Creada por Kent C. Dodds en PayPal, Downshift es una librería primitiva para construir componentes de selección WAI-ARIA compliant como autocomplete, combobox, select y dropdown. Fue uno de los primeros ejemplos populares del patrón headless. Ejemplo con Downshift: Desarrollada por Adobe como parte de React Spectrum, React Aria es una colección de hooks que proporcionan comportamiento y accesibilidad de alta calidad para componentes UI. Es el resultado de investigación exhaustiva sobre accesibilidad y es usado por Adobe en sus propios productos. Ejemplo con React Aria: Por qué React Aria destaca: Radix UI proporciona primitivos de bajo nivel para construir sistemas de diseño de alta calidad y accesibles. Muchas librerías populares como Shadcn/ui están construidas sobre Radix. Ejemplo con Radix UI: Control total sobre el diseño: Puedes implementar exactamente la visión de tu equipo de diseño sin estar limitado por estilos predeterminados o tener que sobrescribir CSS. Cada pixel está bajo tu control. Reutilización con diferentes estilos: La misma lógica puede ser usada en múltiples lugares con apariencias completamente diferentes. Un dropdown puede verse diferente en un panel admin versus una landing page de marketing, compartiendo toda la lógica. Separación perfecta de concerns: La lógica de negocio, manejo de estado y comportamiento están completamente separados de la presentación. Esto hace el código más fácil de mantener, testear y razonar. Accesibilidad incluida: Las librerías headless de calidad han invertido miles de horas en hacer los componentes accesibles. Obtienes ARIA attributes correctos, navegación por teclado, soporte para lectores de pantalla, todo sin tener que convertirte en experto en accesibilidad. Bundle size reducido: Al no incluir CSS ni markup predeterminado, el bundle size es significativamente menor. Solo incluyes la lógica que necesitas. Fácil de testear: La lógica pura sin dependencias de DOM es mucho más fácil de testear unitariamente. Puedes testear el comportamiento sin preocuparte por el markup. Framework agnostic: Muchas librerías headless funcionan en múltiples frameworks. TanStack Table funciona en React, Vue, Solid y Svelte. Portabilidad: Puedes portar la misma lógica a diferentes plataformas. Algunos componentes headless incluso funcionan en React Native. Más trabajo inicial: Tienes que construir el markup y los estilos desde cero. No hay componentes listos para usar out-of-the-box como en una librería de componentes tradicional. Curva de aprendizaje: Requiere entender la API de la librería headless, cómo usar los prop getters o hooks, y cómo componer todo correctamente. La documentación es esencial. No hay UI por defecto: Si necesitas prototipar rápido o simplemente quieres algo que funcione sin personalización, una librería de componentes tradicional será más rápida. Puede ser overkill para casos simples: Si solo necesitas un dropdown básico sin requisitos especiales, usar una librería headless completa puede ser más complejo de lo necesario. Documentación extensa necesaria: Para que otros desarrolladores puedan usar tus componentes basados en headless, necesitas documentar bien cómo se supone que deben ser estilizados y usados. Inconsistencia potencial: Sin componentes prediseñados, diferentes desarrolladores pueden implementar el mismo componente headless de formas inconsistentes en diferentes partes de la aplicación. Debugging puede ser complejo: Cuando algo no funciona, puede ser difícil determinar si el problema está en tu implementación de markup/estilos o en cómo estás usando la librería headless. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK:
import { createContext, useContext, ReactNode, useState } from 'react'; interface TabsContextValue { activeTab: string; setActiveTab: (value: string) => void;
} const TabsContext = createContext<TabsContextValue | undefined>(undefined); export function useTabsContext() { const context = useContext(TabsContext); if (!context) { throw new Error('Tabs compound components must be used within Tabs.Root'); } return context;
} interface TabsProviderProps { children: ReactNode; defaultValue: string;
} export function TabsProvider({ children, defaultValue }: TabsProviderProps) { const [activeTab, setActiveTab] = useState(defaultValue); return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> {children} </TabsContext.Provider> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { createContext, useContext, ReactNode, useState } from 'react'; interface TabsContextValue { activeTab: string; setActiveTab: (value: string) => void;
} const TabsContext = createContext<TabsContextValue | undefined>(undefined); export function useTabsContext() { const context = useContext(TabsContext); if (!context) { throw new Error('Tabs compound components must be used within Tabs.Root'); } return context;
} interface TabsProviderProps { children: ReactNode; defaultValue: string;
} export function TabsProvider({ children, defaultValue }: TabsProviderProps) { const [activeTab, setActiveTab] = useState(defaultValue); return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> {children} </TabsContext.Provider> );
} COMMAND_BLOCK:
import { createContext, useContext, ReactNode, useState } from 'react'; interface TabsContextValue { activeTab: string; setActiveTab: (value: string) => void;
} const TabsContext = createContext<TabsContextValue | undefined>(undefined); export function useTabsContext() { const context = useContext(TabsContext); if (!context) { throw new Error('Tabs compound components must be used within Tabs.Root'); } return context;
} interface TabsProviderProps { children: ReactNode; defaultValue: string;
} export function TabsProvider({ children, defaultValue }: TabsProviderProps) { const [activeTab, setActiveTab] = useState(defaultValue); return ( <TabsContext.Provider value={{ activeTab, setActiveTab }}> {children} </TabsContext.Provider> );
} CODE_BLOCK:
import { ReactNode } from 'react';
import { TabsProvider } from './TabsContext'; interface TabsRootProps { children: ReactNode; defaultValue: string; className?: string;
} export function TabsRoot({ children, defaultValue, className }: TabsRootProps) { return ( <TabsProvider defaultValue={defaultValue}> <div className={className}> {children} </div> </TabsProvider> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { ReactNode } from 'react';
import { TabsProvider } from './TabsContext'; interface TabsRootProps { children: ReactNode; defaultValue: string; className?: string;
} export function TabsRoot({ children, defaultValue, className }: TabsRootProps) { return ( <TabsProvider defaultValue={defaultValue}> <div className={className}> {children} </div> </TabsProvider> );
} CODE_BLOCK:
import { ReactNode } from 'react';
import { TabsProvider } from './TabsContext'; interface TabsRootProps { children: ReactNode; defaultValue: string; className?: string;
} export function TabsRoot({ children, defaultValue, className }: TabsRootProps) { return ( <TabsProvider defaultValue={defaultValue}> <div className={className}> {children} </div> </TabsProvider> );
} CODE_BLOCK:
import { ReactNode } from 'react'; interface TabsListProps { children: ReactNode; className?: string;
} export function TabsList({ children, className }: TabsListProps) { return ( <div role="tablist" className={className}> {children} </div> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { ReactNode } from 'react'; interface TabsListProps { children: ReactNode; className?: string;
} export function TabsList({ children, className }: TabsListProps) { return ( <div role="tablist" className={className}> {children} </div> );
} CODE_BLOCK:
import { ReactNode } from 'react'; interface TabsListProps { children: ReactNode; className?: string;
} export function TabsList({ children, className }: TabsListProps) { return ( <div role="tablist" className={className}> {children} </div> );
} COMMAND_BLOCK:
import { ReactNode } from 'react';
import { useTabsContext } from './TabsContext'; interface TabsTriggerProps { value: string; children: ReactNode; className?: string;
} export function TabsTrigger({ value, children, className }: TabsTriggerProps) { const { activeTab, setActiveTab } = useTabsContext(); const isActive = activeTab === value; return ( <button role="tab" aria-selected={isActive} onClick={() => setActiveTab(value)} className={`${className} ${isActive ? 'active' : ''}`} > {children} </button> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ReactNode } from 'react';
import { useTabsContext } from './TabsContext'; interface TabsTriggerProps { value: string; children: ReactNode; className?: string;
} export function TabsTrigger({ value, children, className }: TabsTriggerProps) { const { activeTab, setActiveTab } = useTabsContext(); const isActive = activeTab === value; return ( <button role="tab" aria-selected={isActive} onClick={() => setActiveTab(value)} className={`${className} ${isActive ? 'active' : ''}`} > {children} </button> );
} COMMAND_BLOCK:
import { ReactNode } from 'react';
import { useTabsContext } from './TabsContext'; interface TabsTriggerProps { value: string; children: ReactNode; className?: string;
} export function TabsTrigger({ value, children, className }: TabsTriggerProps) { const { activeTab, setActiveTab } = useTabsContext(); const isActive = activeTab === value; return ( <button role="tab" aria-selected={isActive} onClick={() => setActiveTab(value)} className={`${className} ${isActive ? 'active' : ''}`} > {children} </button> );
} CODE_BLOCK:
import { ReactNode } from 'react';
import { useTabsContext } from './TabsContext'; interface TabsContentProps { value: string; children: ReactNode; className?: string;
} export function TabsContent({ value, children, className }: TabsContentProps) { const { activeTab } = useTabsContext(); if (activeTab !== value) { return null; } return ( <div role="tabpanel" className={className}> {children} </div> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { ReactNode } from 'react';
import { useTabsContext } from './TabsContext'; interface TabsContentProps { value: string; children: ReactNode; className?: string;
} export function TabsContent({ value, children, className }: TabsContentProps) { const { activeTab } = useTabsContext(); if (activeTab !== value) { return null; } return ( <div role="tabpanel" className={className}> {children} </div> );
} CODE_BLOCK:
import { ReactNode } from 'react';
import { useTabsContext } from './TabsContext'; interface TabsContentProps { value: string; children: ReactNode; className?: string;
} export function TabsContent({ value, children, className }: TabsContentProps) { const { activeTab } = useTabsContext(); if (activeTab !== value) { return null; } return ( <div role="tabpanel" className={className}> {children} </div> );
} CODE_BLOCK:
import { TabsRoot } from './TabsRoot';
import { TabsList } from './TabsList';
import { TabsTrigger } from './TabsTrigger';
import { TabsContent } from './TabsContent'; export const Tabs = { Root: TabsRoot, List: TabsList, Trigger: TabsTrigger, Content: TabsContent,
}; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { TabsRoot } from './TabsRoot';
import { TabsList } from './TabsList';
import { TabsTrigger } from './TabsTrigger';
import { TabsContent } from './TabsContent'; export const Tabs = { Root: TabsRoot, List: TabsList, Trigger: TabsTrigger, Content: TabsContent,
}; CODE_BLOCK:
import { TabsRoot } from './TabsRoot';
import { TabsList } from './TabsList';
import { TabsTrigger } from './TabsTrigger';
import { TabsContent } from './TabsContent'; export const Tabs = { Root: TabsRoot, List: TabsList, Trigger: TabsTrigger, Content: TabsContent,
}; CODE_BLOCK:
import { Tabs } from './components/Tabs'; function App() { return ( <Tabs.Root defaultValue="profile"> <Tabs.List> <Tabs.Trigger value="profile"> Perfil </Tabs.Trigger> <Tabs.Trigger value="settings"> Configuración </Tabs.Trigger> <Tabs.Trigger value="notifications"> Notificaciones </Tabs.Trigger> </Tabs.List> <Tabs.Content value="profile"> <h2>Tu Perfil</h2> <p>Información de tu cuenta y preferencias personales.</p> </Tabs.Content> <Tabs.Content value="settings"> <h2>Configuración</h2> <p>Ajusta las opciones de tu aplicación.</p> </Tabs.Content> <Tabs.Content value="notifications"> <h2>Notificaciones</h2> <p>Gestiona tus alertas y notificaciones.</p> </Tabs.Content> </Tabs.Root> );
} export default App; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Tabs } from './components/Tabs'; function App() { return ( <Tabs.Root defaultValue="profile"> <Tabs.List> <Tabs.Trigger value="profile"> Perfil </Tabs.Trigger> <Tabs.Trigger value="settings"> Configuración </Tabs.Trigger> <Tabs.Trigger value="notifications"> Notificaciones </Tabs.Trigger> </Tabs.List> <Tabs.Content value="profile"> <h2>Tu Perfil</h2> <p>Información de tu cuenta y preferencias personales.</p> </Tabs.Content> <Tabs.Content value="settings"> <h2>Configuración</h2> <p>Ajusta las opciones de tu aplicación.</p> </Tabs.Content> <Tabs.Content value="notifications"> <h2>Notificaciones</h2> <p>Gestiona tus alertas y notificaciones.</p> </Tabs.Content> </Tabs.Root> );
} export default App; CODE_BLOCK:
import { Tabs } from './components/Tabs'; function App() { return ( <Tabs.Root defaultValue="profile"> <Tabs.List> <Tabs.Trigger value="profile"> Perfil </Tabs.Trigger> <Tabs.Trigger value="settings"> Configuración </Tabs.Trigger> <Tabs.Trigger value="notifications"> Notificaciones </Tabs.Trigger> </Tabs.List> <Tabs.Content value="profile"> <h2>Tu Perfil</h2> <p>Información de tu cuenta y preferencias personales.</p> </Tabs.Content> <Tabs.Content value="settings"> <h2>Configuración</h2> <p>Ajusta las opciones de tu aplicación.</p> </Tabs.Content> <Tabs.Content value="notifications"> <h2>Notificaciones</h2> <p>Gestiona tus alertas y notificaciones.</p> </Tabs.Content> </Tabs.Root> );
} export default App; CODE_BLOCK:
import * as Accordion from '@radix-ui/react-accordion'; function MyAccordion() { return ( <Accordion.Root type="single" collapsible> <Accordion.Item value="item-1"> <Accordion.Trigger>¿Qué es Radix UI?</Accordion.Trigger> <Accordion.Content> Una librería de componentes primitivos sin estilos. </Accordion.Content> </Accordion.Item> <Accordion.Item value="item-2"> <Accordion.Trigger>¿Por qué usar Compound Components?</Accordion.Trigger> <Accordion.Content> Proporciona flexibilidad y una API expresiva. </Accordion.Content> </Accordion.Item> </Accordion.Root> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import * as Accordion from '@radix-ui/react-accordion'; function MyAccordion() { return ( <Accordion.Root type="single" collapsible> <Accordion.Item value="item-1"> <Accordion.Trigger>¿Qué es Radix UI?</Accordion.Trigger> <Accordion.Content> Una librería de componentes primitivos sin estilos. </Accordion.Content> </Accordion.Item> <Accordion.Item value="item-2"> <Accordion.Trigger>¿Por qué usar Compound Components?</Accordion.Trigger> <Accordion.Content> Proporciona flexibilidad y una API expresiva. </Accordion.Content> </Accordion.Item> </Accordion.Root> );
} CODE_BLOCK:
import * as Accordion from '@radix-ui/react-accordion'; function MyAccordion() { return ( <Accordion.Root type="single" collapsible> <Accordion.Item value="item-1"> <Accordion.Trigger>¿Qué es Radix UI?</Accordion.Trigger> <Accordion.Content> Una librería de componentes primitivos sin estilos. </Accordion.Content> </Accordion.Item> <Accordion.Item value="item-2"> <Accordion.Trigger>¿Por qué usar Compound Components?</Accordion.Trigger> <Accordion.Content> Proporciona flexibilidad y una API expresiva. </Accordion.Content> </Accordion.Item> </Accordion.Root> );
} COMMAND_BLOCK:
import { Menu } from '@headlessui/react'; function MyMenu() { return ( <Menu> <Menu.Button>Opciones</Menu.Button> <Menu.Items> <Menu.Item> {({ active }) => ( <a className={active ? 'bg-blue-500' : ''} href="/account"> Mi Cuenta </a> )} </Menu.Item> <Menu.Item> {({ active }) => ( <a className={active ? 'bg-blue-500' : ''} href="/settings"> Configuración </a> )} </Menu.Item> </Menu.Items> </Menu> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Menu } from '@headlessui/react'; function MyMenu() { return ( <Menu> <Menu.Button>Opciones</Menu.Button> <Menu.Items> <Menu.Item> {({ active }) => ( <a className={active ? 'bg-blue-500' : ''} href="/account"> Mi Cuenta </a> )} </Menu.Item> <Menu.Item> {({ active }) => ( <a className={active ? 'bg-blue-500' : ''} href="/settings"> Configuración </a> )} </Menu.Item> </Menu.Items> </Menu> );
} COMMAND_BLOCK:
import { Menu } from '@headlessui/react'; function MyMenu() { return ( <Menu> <Menu.Button>Opciones</Menu.Button> <Menu.Items> <Menu.Item> {({ active }) => ( <a className={active ? 'bg-blue-500' : ''} href="/account"> Mi Cuenta </a> )} </Menu.Item> <Menu.Item> {({ active }) => ( <a className={active ? 'bg-blue-500' : ''} href="/settings"> Configuración </a> )} </Menu.Item> </Menu.Items> </Menu> );
} CODE_BLOCK:
import { Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react'; function ChakraTabs() { return ( <Tabs> <TabList> <Tab>Inicio</Tab> <Tab>Proyectos</Tab> <Tab>Contacto</Tab> </TabList> <TabPanels> <TabPanel> <p>Contenido de inicio</p> </TabPanel> <TabPanel> <p>Contenido de proyectos</p> </TabPanel> <TabPanel> <p>Contenido de contacto</p> </TabPanel> </TabPanels> </Tabs> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react'; function ChakraTabs() { return ( <Tabs> <TabList> <Tab>Inicio</Tab> <Tab>Proyectos</Tab> <Tab>Contacto</Tab> </TabList> <TabPanels> <TabPanel> <p>Contenido de inicio</p> </TabPanel> <TabPanel> <p>Contenido de proyectos</p> </TabPanel> <TabPanel> <p>Contenido de contacto</p> </TabPanel> </TabPanels> </Tabs> );
} CODE_BLOCK:
import { Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react'; function ChakraTabs() { return ( <Tabs> <TabList> <Tab>Inicio</Tab> <Tab>Proyectos</Tab> <Tab>Contacto</Tab> </TabList> <TabPanels> <TabPanel> <p>Contenido de inicio</p> </TabPanel> <TabPanel> <p>Contenido de proyectos</p> </TabPanel> <TabPanel> <p>Contenido de contacto</p> </TabPanel> </TabPanels> </Tabs> );
} CODE_BLOCK:
import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@reach/ui/tabs'; function ReachTabs() { return ( <Tabs> <TabList> <Tab>Uno</Tab> <Tab>Dos</Tab> <Tab>Tres</Tab> </TabList> <TabPanels> <TabPanel> <p>Panel uno</p> </TabPanel> <TabPanel> <p>Panel dos</p> </TabPanel> <TabPanel> <p>Panel tres</p> </TabPanel> </TabPanels> </Tabs> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@reach/ui/tabs'; function ReachTabs() { return ( <Tabs> <TabList> <Tab>Uno</Tab> <Tab>Dos</Tab> <Tab>Tres</Tab> </TabList> <TabPanels> <TabPanel> <p>Panel uno</p> </TabPanel> <TabPanel> <p>Panel dos</p> </TabPanel> <TabPanel> <p>Panel tres</p> </TabPanel> </TabPanels> </Tabs> );
} CODE_BLOCK:
import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@reach/ui/tabs'; function ReachTabs() { return ( <Tabs> <TabList> <Tab>Uno</Tab> <Tab>Dos</Tab> <Tab>Tres</Tab> </TabList> <TabPanels> <TabPanel> <p>Panel uno</p> </TabPanel> <TabPanel> <p>Panel dos</p> </TabPanel> <TabPanel> <p>Panel tres</p> </TabPanel> </TabPanels> </Tabs> );
} CODE_BLOCK:
import { ReactNode } from 'react'; export interface CardProps { header?: ReactNode; media?: ReactNode; footer?: ReactNode; actions?: ReactNode; children: ReactNode; variant?: 'default' | 'elevated' | 'outlined'; className?: string;
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { ReactNode } from 'react'; export interface CardProps { header?: ReactNode; media?: ReactNode; footer?: ReactNode; actions?: ReactNode; children: ReactNode; variant?: 'default' | 'elevated' | 'outlined'; className?: string;
} CODE_BLOCK:
import { ReactNode } from 'react'; export interface CardProps { header?: ReactNode; media?: ReactNode; footer?: ReactNode; actions?: ReactNode; children: ReactNode; variant?: 'default' | 'elevated' | 'outlined'; className?: string;
} CODE_BLOCK:
import { CardProps } from './CardTypes';
import './Card.css'; export function Card({ header, media, footer, actions, children, variant = 'default', className = '',
}: CardProps) { return ( <div className={`card card--${variant} ${className}`}> {media && <div className="card__media">{media}</div>} {header && <div className="card__header">{header}</div>} <div className="card__content">{children}</div> {actions && <div className="card__actions">{actions}</div>} {footer && <div className="card__footer">{footer}</div>} </div> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { CardProps } from './CardTypes';
import './Card.css'; export function Card({ header, media, footer, actions, children, variant = 'default', className = '',
}: CardProps) { return ( <div className={`card card--${variant} ${className}`}> {media && <div className="card__media">{media}</div>} {header && <div className="card__header">{header}</div>} <div className="card__content">{children}</div> {actions && <div className="card__actions">{actions}</div>} {footer && <div className="card__footer">{footer}</div>} </div> );
} CODE_BLOCK:
import { CardProps } from './CardTypes';
import './Card.css'; export function Card({ header, media, footer, actions, children, variant = 'default', className = '',
}: CardProps) { return ( <div className={`card card--${variant} ${className}`}> {media && <div className="card__media">{media}</div>} {header && <div className="card__header">{header}</div>} <div className="card__content">{children}</div> {actions && <div className="card__actions">{actions}</div>} {footer && <div className="card__footer">{footer}</div>} </div> );
} COMMAND_BLOCK:
import { Card } from './Card'; interface Product { id: string; name: string; price: number; originalPrice?: number; image: string; description: string; features: string[]; badge?: string;
} interface ProductCardProps { product: Product; onAddToCart: (productId: string) => void; onViewDetails: (productId: string) => void;
} export function ProductCard({ product, onAddToCart, onViewDetails }: ProductCardProps) { return ( <Card variant="elevated" media={ <div className="product-card__media-wrapper"> <img src={product.image} alt={product.name} className="product-card__image" /> {product.badge && ( <span className="product-card__badge"> {product.badge} </span> )} </div> } header={ <div className="product-card__header"> <h3 className="product-card__title">{product.name}</h3> <div className="product-card__price"> {product.originalPrice && ( <span className="product-card__price--original"> ${product.originalPrice} </span> )} <span className="product-card__price--current"> ${product.price} </span> </div> </div> } footer={ <div className="product-card__footer"> <span className="product-card__shipping"> Envío gratis </span> <span className="product-card__stock"> En stock </span> </div> } actions={ <div className="product-card__actions"> <button onClick={() => onViewDetails(product.id)} className="button button--secondary" > Ver detalles </button> <button onClick={() => onAddToCart(product.id)} className="button button--primary" > Agregar al carrito </button> </div> } > <p className="product-card__description"> {product.description} </p> <ul className="product-card__features"> {product.features.map((feature, index) => ( <li key={index}>{feature}</li> ))} </ul> </Card> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Card } from './Card'; interface Product { id: string; name: string; price: number; originalPrice?: number; image: string; description: string; features: string[]; badge?: string;
} interface ProductCardProps { product: Product; onAddToCart: (productId: string) => void; onViewDetails: (productId: string) => void;
} export function ProductCard({ product, onAddToCart, onViewDetails }: ProductCardProps) { return ( <Card variant="elevated" media={ <div className="product-card__media-wrapper"> <img src={product.image} alt={product.name} className="product-card__image" /> {product.badge && ( <span className="product-card__badge"> {product.badge} </span> )} </div> } header={ <div className="product-card__header"> <h3 className="product-card__title">{product.name}</h3> <div className="product-card__price"> {product.originalPrice && ( <span className="product-card__price--original"> ${product.originalPrice} </span> )} <span className="product-card__price--current"> ${product.price} </span> </div> </div> } footer={ <div className="product-card__footer"> <span className="product-card__shipping"> Envío gratis </span> <span className="product-card__stock"> En stock </span> </div> } actions={ <div className="product-card__actions"> <button onClick={() => onViewDetails(product.id)} className="button button--secondary" > Ver detalles </button> <button onClick={() => onAddToCart(product.id)} className="button button--primary" > Agregar al carrito </button> </div> } > <p className="product-card__description"> {product.description} </p> <ul className="product-card__features"> {product.features.map((feature, index) => ( <li key={index}>{feature}</li> ))} </ul> </Card> );
} COMMAND_BLOCK:
import { Card } from './Card'; interface Product { id: string; name: string; price: number; originalPrice?: number; image: string; description: string; features: string[]; badge?: string;
} interface ProductCardProps { product: Product; onAddToCart: (productId: string) => void; onViewDetails: (productId: string) => void;
} export function ProductCard({ product, onAddToCart, onViewDetails }: ProductCardProps) { return ( <Card variant="elevated" media={ <div className="product-card__media-wrapper"> <img src={product.image} alt={product.name} className="product-card__image" /> {product.badge && ( <span className="product-card__badge"> {product.badge} </span> )} </div> } header={ <div className="product-card__header"> <h3 className="product-card__title">{product.name}</h3> <div className="product-card__price"> {product.originalPrice && ( <span className="product-card__price--original"> ${product.originalPrice} </span> )} <span className="product-card__price--current"> ${product.price} </span> </div> </div> } footer={ <div className="product-card__footer"> <span className="product-card__shipping"> Envío gratis </span> <span className="product-card__stock"> En stock </span> </div> } actions={ <div className="product-card__actions"> <button onClick={() => onViewDetails(product.id)} className="button button--secondary" > Ver detalles </button> <button onClick={() => onAddToCart(product.id)} className="button button--primary" > Agregar al carrito </button> </div> } > <p className="product-card__description"> {product.description} </p> <ul className="product-card__features"> {product.features.map((feature, index) => ( <li key={index}>{feature}</li> ))} </ul> </Card> );
} CODE_BLOCK:
import { ReactNode } from 'react'; interface DashboardLayoutProps { header: ReactNode; sidebar: ReactNode; notifications?: ReactNode; children: ReactNode;
} export function DashboardLayout({ header, sidebar, notifications, children,
}: DashboardLayoutProps) { return ( <div className="dashboard-layout"> <header className="dashboard-layout__header"> {header} {notifications && ( <div className="dashboard-layout__notifications"> {notifications} </div> )} </header> <div className="dashboard-layout__body"> <aside className="dashboard-layout__sidebar"> {sidebar} </aside> <main className="dashboard-layout__main"> {children} </main> </div> </div> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { ReactNode } from 'react'; interface DashboardLayoutProps { header: ReactNode; sidebar: ReactNode; notifications?: ReactNode; children: ReactNode;
} export function DashboardLayout({ header, sidebar, notifications, children,
}: DashboardLayoutProps) { return ( <div className="dashboard-layout"> <header className="dashboard-layout__header"> {header} {notifications && ( <div className="dashboard-layout__notifications"> {notifications} </div> )} </header> <div className="dashboard-layout__body"> <aside className="dashboard-layout__sidebar"> {sidebar} </aside> <main className="dashboard-layout__main"> {children} </main> </div> </div> );
} CODE_BLOCK:
import { ReactNode } from 'react'; interface DashboardLayoutProps { header: ReactNode; sidebar: ReactNode; notifications?: ReactNode; children: ReactNode;
} export function DashboardLayout({ header, sidebar, notifications, children,
}: DashboardLayoutProps) { return ( <div className="dashboard-layout"> <header className="dashboard-layout__header"> {header} {notifications && ( <div className="dashboard-layout__notifications"> {notifications} </div> )} </header> <div className="dashboard-layout__body"> <aside className="dashboard-layout__sidebar"> {sidebar} </aside> <main className="dashboard-layout__main"> {children} </main> </div> </div> );
} COMMAND_BLOCK:
import { DashboardLayout } from './DashboardLayout';
import { ProductCard } from './ProductCard'; function App() { const product = { id: '1', name: 'Producto Premium', price: 79, originalPrice: 99, image: '/product.jpg', description: 'Descripción del producto con características increíbles.', features: [ 'Alta calidad', 'Garantía extendida', 'Soporte 24/7' ], badge: 'Nuevo' }; return ( <DashboardLayout header={ <div className="header"> <h1>Mi Dashboard</h1> <button>Cerrar sesión</button> </div> } sidebar={ <nav> <ul> <li>Inicio</li> <li>Productos</li> <li>Configuración</li> </ul> </nav> } notifications={ <div className="notification-badge"> 3 nuevas notificaciones </div> } > <div className="products-grid"> <ProductCard product={product} onAddToCart={(id) => console.log('Añadido:', id)} onViewDetails={(id) => console.log('Ver:', id)} /> </div> </DashboardLayout> );
} export default App; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { DashboardLayout } from './DashboardLayout';
import { ProductCard } from './ProductCard'; function App() { const product = { id: '1', name: 'Producto Premium', price: 79, originalPrice: 99, image: '/product.jpg', description: 'Descripción del producto con características increíbles.', features: [ 'Alta calidad', 'Garantía extendida', 'Soporte 24/7' ], badge: 'Nuevo' }; return ( <DashboardLayout header={ <div className="header"> <h1>Mi Dashboard</h1> <button>Cerrar sesión</button> </div> } sidebar={ <nav> <ul> <li>Inicio</li> <li>Productos</li> <li>Configuración</li> </ul> </nav> } notifications={ <div className="notification-badge"> 3 nuevas notificaciones </div> } > <div className="products-grid"> <ProductCard product={product} onAddToCart={(id) => console.log('Añadido:', id)} onViewDetails={(id) => console.log('Ver:', id)} /> </div> </DashboardLayout> );
} export default App; COMMAND_BLOCK:
import { DashboardLayout } from './DashboardLayout';
import { ProductCard } from './ProductCard'; function App() { const product = { id: '1', name: 'Producto Premium', price: 79, originalPrice: 99, image: '/product.jpg', description: 'Descripción del producto con características increíbles.', features: [ 'Alta calidad', 'Garantía extendida', 'Soporte 24/7' ], badge: 'Nuevo' }; return ( <DashboardLayout header={ <div className="header"> <h1>Mi Dashboard</h1> <button>Cerrar sesión</button> </div> } sidebar={ <nav> <ul> <li>Inicio</li> <li>Productos</li> <li>Configuración</li> </ul> </nav> } notifications={ <div className="notification-badge"> 3 nuevas notificaciones </div> } > <div className="products-grid"> <ProductCard product={product} onAddToCart={(id) => console.log('Añadido:', id)} onViewDetails={(id) => console.log('Ver:', id)} /> </div> </DashboardLayout> );
} export default App; CODE_BLOCK:
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { TextField } from '@mui/material';
import CalendarIcon from '@mui/icons-material/CalendarMonth'; function MyDatePicker() { return ( <DatePicker slots={{ openPickerIcon: CalendarIcon, textField: TextField, }} slotProps={{ openPickerIcon: { color: 'primary', }, textField: { variant: 'outlined', label: 'Seleccionar fecha', }, }} /> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { TextField } from '@mui/material';
import CalendarIcon from '@mui/icons-material/CalendarMonth'; function MyDatePicker() { return ( <DatePicker slots={{ openPickerIcon: CalendarIcon, textField: TextField, }} slotProps={{ openPickerIcon: { color: 'primary', }, textField: { variant: 'outlined', label: 'Seleccionar fecha', }, }} /> );
} CODE_BLOCK:
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { TextField } from '@mui/material';
import CalendarIcon from '@mui/icons-material/CalendarMonth'; function MyDatePicker() { return ( <DatePicker slots={{ openPickerIcon: CalendarIcon, textField: TextField, }} slotProps={{ openPickerIcon: { color: 'primary', }, textField: { variant: 'outlined', label: 'Seleccionar fecha', }, }} /> );
} CODE_BLOCK:
import { Card, CardContent, CardActions, CardHeader, CardMedia } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import MoreVertIcon from '@mui/icons-material/MoreVert'; function MUICard() { return ( <Card> <CardHeader avatar={ <Avatar sx={{ bgcolor: 'primary.main' }}> R </Avatar> } action={ <IconButton> <MoreVertIcon /> </IconButton> } title="Título de la tarjeta" subheader="Subtítulo" /> <CardMedia component="img" height="200" image="/image.jpg" alt="Descripción" /> <CardContent> <p>Contenido de la tarjeta</p> </CardContent> <CardActions> <button>Acción 1</button> <button>Acción 2</button> </CardActions> </Card> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Card, CardContent, CardActions, CardHeader, CardMedia } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import MoreVertIcon from '@mui/icons-material/MoreVert'; function MUICard() { return ( <Card> <CardHeader avatar={ <Avatar sx={{ bgcolor: 'primary.main' }}> R </Avatar> } action={ <IconButton> <MoreVertIcon /> </IconButton> } title="Título de la tarjeta" subheader="Subtítulo" /> <CardMedia component="img" height="200" image="/image.jpg" alt="Descripción" /> <CardContent> <p>Contenido de la tarjeta</p> </CardContent> <CardActions> <button>Acción 1</button> <button>Acción 2</button> </CardActions> </Card> );
} CODE_BLOCK:
import { Card, CardContent, CardActions, CardHeader, CardMedia } from '@mui/material';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import MoreVertIcon from '@mui/icons-material/MoreVert'; function MUICard() { return ( <Card> <CardHeader avatar={ <Avatar sx={{ bgcolor: 'primary.main' }}> R </Avatar> } action={ <IconButton> <MoreVertIcon /> </IconButton> } title="Título de la tarjeta" subheader="Subtítulo" /> <CardMedia component="img" height="200" image="/image.jpg" alt="Descripción" /> <CardContent> <p>Contenido de la tarjeta</p> </CardContent> <CardActions> <button>Acción 1</button> <button>Acción 2</button> </CardActions> </Card> );
} CODE_BLOCK:
// app/layout.tsx
interface RootLayoutProps { children: React.ReactNode;
} export default function RootLayout({ children }: RootLayoutProps) { return ( <html lang="es"> <body> <header> <nav>Mi Aplicación</nav> </header> <main>{children}</main> <footer>Copyright 2026</footer> </body> </html> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
// app/layout.tsx
interface RootLayoutProps { children: React.ReactNode;
} export default function RootLayout({ children }: RootLayoutProps) { return ( <html lang="es"> <body> <header> <nav>Mi Aplicación</nav> </header> <main>{children}</main> <footer>Copyright 2026</footer> </body> </html> );
} CODE_BLOCK:
// app/layout.tsx
interface RootLayoutProps { children: React.ReactNode;
} export default function RootLayout({ children }: RootLayoutProps) { return ( <html lang="es"> <body> <header> <nav>Mi Aplicación</nav> </header> <main>{children}</main> <footer>Copyright 2026</footer> </body> </html> );
} CODE_BLOCK:
// app/dashboard/layout.tsx
interface DashboardLayoutProps { children: React.ReactNode; analytics: React.ReactNode; // slot @analytics team: React.ReactNode; // slot @team
} export default function DashboardLayout({ children, analytics, team,
}: DashboardLayoutProps) { return ( <div className="dashboard"> <div className="dashboard__main"> {children} </div> <aside className="dashboard__sidebar"> <section className="dashboard__analytics"> {analytics} </section> <section className="dashboard__team"> {team} </section> </aside> </div> );
} // app/dashboard/@analytics/page.tsx
export default function Analytics() { return ( <div> <h2>Analíticas</h2> <p>Métricas del dashboard</p> </div> );
} // app/dashboard/@team/page.tsx
export default function Team() { return ( <div> <h2>Equipo</h2> <p>Información del equipo</p> </div> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
// app/dashboard/layout.tsx
interface DashboardLayoutProps { children: React.ReactNode; analytics: React.ReactNode; // slot @analytics team: React.ReactNode; // slot @team
} export default function DashboardLayout({ children, analytics, team,
}: DashboardLayoutProps) { return ( <div className="dashboard"> <div className="dashboard__main"> {children} </div> <aside className="dashboard__sidebar"> <section className="dashboard__analytics"> {analytics} </section> <section className="dashboard__team"> {team} </section> </aside> </div> );
} // app/dashboard/@analytics/page.tsx
export default function Analytics() { return ( <div> <h2>Analíticas</h2> <p>Métricas del dashboard</p> </div> );
} // app/dashboard/@team/page.tsx
export default function Team() { return ( <div> <h2>Equipo</h2> <p>Información del equipo</p> </div> );
} CODE_BLOCK:
// app/dashboard/layout.tsx
interface DashboardLayoutProps { children: React.ReactNode; analytics: React.ReactNode; // slot @analytics team: React.ReactNode; // slot @team
} export default function DashboardLayout({ children, analytics, team,
}: DashboardLayoutProps) { return ( <div className="dashboard"> <div className="dashboard__main"> {children} </div> <aside className="dashboard__sidebar"> <section className="dashboard__analytics"> {analytics} </section> <section className="dashboard__team"> {team} </section> </aside> </div> );
} // app/dashboard/@analytics/page.tsx
export default function Analytics() { return ( <div> <h2>Analíticas</h2> <p>Métricas del dashboard</p> </div> );
} // app/dashboard/@team/page.tsx
export default function Team() { return ( <div> <h2>Equipo</h2> <p>Información del equipo</p> </div> );
} CODE_BLOCK:
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button"; function ShadcnCard() { return ( <Card className="w-[350px]"> <CardHeader> <CardTitle>Crear proyecto</CardTitle> <CardDescription> Despliega tu nuevo proyecto en un solo click. </CardDescription> </CardHeader> <CardContent> <form> <div className="grid w-full items-center gap-4"> <div className="flex flex-col space-y-1.5"> <label htmlFor="name">Nombre</label> <input id="name" placeholder="Nombre del proyecto" /> </div> </div> </form> </CardContent> <CardFooter className="flex justify-between"> <Button variant="outline">Cancelar</Button> <Button>Desplegar</Button> </CardFooter> </Card> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button"; function ShadcnCard() { return ( <Card className="w-[350px]"> <CardHeader> <CardTitle>Crear proyecto</CardTitle> <CardDescription> Despliega tu nuevo proyecto en un solo click. </CardDescription> </CardHeader> <CardContent> <form> <div className="grid w-full items-center gap-4"> <div className="flex flex-col space-y-1.5"> <label htmlFor="name">Nombre</label> <input id="name" placeholder="Nombre del proyecto" /> </div> </div> </form> </CardContent> <CardFooter className="flex justify-between"> <Button variant="outline">Cancelar</Button> <Button>Desplegar</Button> </CardFooter> </Card> );
} CODE_BLOCK:
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button"; function ShadcnCard() { return ( <Card className="w-[350px]"> <CardHeader> <CardTitle>Crear proyecto</CardTitle> <CardDescription> Despliega tu nuevo proyecto en un solo click. </CardDescription> </CardHeader> <CardContent> <form> <div className="grid w-full items-center gap-4"> <div className="flex flex-col space-y-1.5"> <label htmlFor="name">Nombre</label> <input id="name" placeholder="Nombre del proyecto" /> </div> </div> </form> </CardContent> <CardFooter className="flex justify-between"> <Button variant="outline">Cancelar</Button> <Button>Desplegar</Button> </CardFooter> </Card> );
} CODE_BLOCK:
import { Card, Avatar } from 'antd';
import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; const { Meta } = Card; function AntCard() { return ( <Card style={{ width: 300 }} cover={ <img alt="example" src="https://example.com/image.jpg" /> } actions={[ <SettingOutlined key="setting" />, <EditOutlined key="edit" />, <EllipsisOutlined key="ellipsis" />, ]} > <Meta avatar={<Avatar src="https://example.com/avatar.jpg" />} title="Card title" description="This is the description" /> </Card> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Card, Avatar } from 'antd';
import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; const { Meta } = Card; function AntCard() { return ( <Card style={{ width: 300 }} cover={ <img alt="example" src="https://example.com/image.jpg" /> } actions={[ <SettingOutlined key="setting" />, <EditOutlined key="edit" />, <EllipsisOutlined key="ellipsis" />, ]} > <Meta avatar={<Avatar src="https://example.com/avatar.jpg" />} title="Card title" description="This is the description" /> </Card> );
} CODE_BLOCK:
import { Card, Avatar } from 'antd';
import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'; const { Meta } = Card; function AntCard() { return ( <Card style={{ width: 300 }} cover={ <img alt="example" src="https://example.com/image.jpg" /> } actions={[ <SettingOutlined key="setting" />, <EditOutlined key="edit" />, <EllipsisOutlined key="ellipsis" />, ]} > <Meta avatar={<Avatar src="https://example.com/avatar.jpg" />} title="Card title" description="This is the description" /> </Card> );
} CODE_BLOCK:
function withSomething(Component: ComponentType): ComponentType Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
function withSomething(Component: ComponentType): ComponentType CODE_BLOCK:
function withSomething(Component: ComponentType): ComponentType COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; interface AuthUser { id: string; name: string; email: string; roles: string[];
} interface WithAuthProps { user: AuthUser;
} interface WithAuthOptions { redirectTo?: string; requiredRoles?: string[]; LoadingComponent?: ComponentType;
} function useAuth() { // Simulación de hook de autenticación // En una app real, esto vendría de tu sistema de auth const user: AuthUser | null = { id: '1', name: 'John Doe', email: '[email protected]', roles: ['user'], }; const loading = false; return { user, loading };
} export function withAuth<P extends WithAuthProps>( Component: ComponentType<P>, options: WithAuthOptions = {}
) { const { redirectTo = '/login', requiredRoles = [], LoadingComponent = () => <div>Loading...</div>, } = options; return function AuthenticatedComponent( props: Omit<P, keyof WithAuthProps> ) { const { user, loading } = useAuth(); const navigate = useNavigate(); useEffect(() => { if (!loading && !user) { navigate(redirectTo); return; } if (user && requiredRoles.length > 0) { const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role) ); if (!hasRequiredRole) { navigate('/unauthorized'); } } }, [user, loading, navigate]); if (loading) { return <LoadingComponent />; } if (!user) { return null; } return <Component {...(props as P)} user={user} />; };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; interface AuthUser { id: string; name: string; email: string; roles: string[];
} interface WithAuthProps { user: AuthUser;
} interface WithAuthOptions { redirectTo?: string; requiredRoles?: string[]; LoadingComponent?: ComponentType;
} function useAuth() { // Simulación de hook de autenticación // En una app real, esto vendría de tu sistema de auth const user: AuthUser | null = { id: '1', name: 'John Doe', email: '[email protected]', roles: ['user'], }; const loading = false; return { user, loading };
} export function withAuth<P extends WithAuthProps>( Component: ComponentType<P>, options: WithAuthOptions = {}
) { const { redirectTo = '/login', requiredRoles = [], LoadingComponent = () => <div>Loading...</div>, } = options; return function AuthenticatedComponent( props: Omit<P, keyof WithAuthProps> ) { const { user, loading } = useAuth(); const navigate = useNavigate(); useEffect(() => { if (!loading && !user) { navigate(redirectTo); return; } if (user && requiredRoles.length > 0) { const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role) ); if (!hasRequiredRole) { navigate('/unauthorized'); } } }, [user, loading, navigate]); if (loading) { return <LoadingComponent />; } if (!user) { return null; } return <Component {...(props as P)} user={user} />; };
} COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; interface AuthUser { id: string; name: string; email: string; roles: string[];
} interface WithAuthProps { user: AuthUser;
} interface WithAuthOptions { redirectTo?: string; requiredRoles?: string[]; LoadingComponent?: ComponentType;
} function useAuth() { // Simulación de hook de autenticación // En una app real, esto vendría de tu sistema de auth const user: AuthUser | null = { id: '1', name: 'John Doe', email: '[email protected]', roles: ['user'], }; const loading = false; return { user, loading };
} export function withAuth<P extends WithAuthProps>( Component: ComponentType<P>, options: WithAuthOptions = {}
) { const { redirectTo = '/login', requiredRoles = [], LoadingComponent = () => <div>Loading...</div>, } = options; return function AuthenticatedComponent( props: Omit<P, keyof WithAuthProps> ) { const { user, loading } = useAuth(); const navigate = useNavigate(); useEffect(() => { if (!loading && !user) { navigate(redirectTo); return; } if (user && requiredRoles.length > 0) { const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role) ); if (!hasRequiredRole) { navigate('/unauthorized'); } } }, [user, loading, navigate]); if (loading) { return <LoadingComponent />; } if (!user) { return null; } return <Component {...(props as P)} user={user} />; };
} COMMAND_BLOCK:
import { Component, ComponentType, ErrorInfo, ReactNode } from 'react'; interface ErrorBoundaryState { hasError: boolean; error: Error | null;
} interface ErrorFallbackProps { error: Error | null; resetError: () => void;
} const DefaultErrorFallback = ({ error, resetError }: ErrorFallbackProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <h2>Something went wrong</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {error?.message} </details> <button onClick={resetError}>Try again</button> </div>
); export function withErrorBoundary<P extends object>( WrappedComponent: ComponentType<P>, ErrorFallback: ComponentType<ErrorFallbackProps> = DefaultErrorFallback
) { return class WithErrorBoundary extends Component<P, ErrorBoundaryState> { constructor(props: P) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('Error caught by boundary:', error, errorInfo); // Aquí podrías enviar el error a un servicio de logging // logErrorToService(error, errorInfo); } resetError = (): void => { this.setState({ hasError: false, error: null }); }; render(): ReactNode { if (this.state.hasError) { return ( <ErrorFallback error={this.state.error} resetError={this.resetError} /> ); } return <WrappedComponent {...this.props} />; } };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Component, ComponentType, ErrorInfo, ReactNode } from 'react'; interface ErrorBoundaryState { hasError: boolean; error: Error | null;
} interface ErrorFallbackProps { error: Error | null; resetError: () => void;
} const DefaultErrorFallback = ({ error, resetError }: ErrorFallbackProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <h2>Something went wrong</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {error?.message} </details> <button onClick={resetError}>Try again</button> </div>
); export function withErrorBoundary<P extends object>( WrappedComponent: ComponentType<P>, ErrorFallback: ComponentType<ErrorFallbackProps> = DefaultErrorFallback
) { return class WithErrorBoundary extends Component<P, ErrorBoundaryState> { constructor(props: P) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('Error caught by boundary:', error, errorInfo); // Aquí podrías enviar el error a un servicio de logging // logErrorToService(error, errorInfo); } resetError = (): void => { this.setState({ hasError: false, error: null }); }; render(): ReactNode { if (this.state.hasError) { return ( <ErrorFallback error={this.state.error} resetError={this.resetError} /> ); } return <WrappedComponent {...this.props} />; } };
} COMMAND_BLOCK:
import { Component, ComponentType, ErrorInfo, ReactNode } from 'react'; interface ErrorBoundaryState { hasError: boolean; error: Error | null;
} interface ErrorFallbackProps { error: Error | null; resetError: () => void;
} const DefaultErrorFallback = ({ error, resetError }: ErrorFallbackProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <h2>Something went wrong</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {error?.message} </details> <button onClick={resetError}>Try again</button> </div>
); export function withErrorBoundary<P extends object>( WrappedComponent: ComponentType<P>, ErrorFallback: ComponentType<ErrorFallbackProps> = DefaultErrorFallback
) { return class WithErrorBoundary extends Component<P, ErrorBoundaryState> { constructor(props: P) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('Error caught by boundary:', error, errorInfo); // Aquí podrías enviar el error a un servicio de logging // logErrorToService(error, errorInfo); } resetError = (): void => { this.setState({ hasError: false, error: null }); }; render(): ReactNode { if (this.state.hasError) { return ( <ErrorFallback error={this.state.error} resetError={this.resetError} /> ); } return <WrappedComponent {...this.props} />; } };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; interface WithLoadingProps { isLoading: boolean;
} interface LoadingComponentProps { message?: string;
} const DefaultLoadingComponent = ({ message }: LoadingComponentProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <div>Loading{message ? `: ${message}` : '...'}</div> </div>
); export function withLoading<P extends WithLoadingProps>( Component: ComponentType<P>, LoadingComponent: ComponentType<LoadingComponentProps> = DefaultLoadingComponent, loadingMessage?: string
) { return function WithLoadingComponent(props: P) { if (props.isLoading) { return <LoadingComponent message={loadingMessage} />; } return <Component {...props} />; };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType } from 'react'; interface WithLoadingProps { isLoading: boolean;
} interface LoadingComponentProps { message?: string;
} const DefaultLoadingComponent = ({ message }: LoadingComponentProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <div>Loading{message ? `: ${message}` : '...'}</div> </div>
); export function withLoading<P extends WithLoadingProps>( Component: ComponentType<P>, LoadingComponent: ComponentType<LoadingComponentProps> = DefaultLoadingComponent, loadingMessage?: string
) { return function WithLoadingComponent(props: P) { if (props.isLoading) { return <LoadingComponent message={loadingMessage} />; } return <Component {...props} />; };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; interface WithLoadingProps { isLoading: boolean;
} interface LoadingComponentProps { message?: string;
} const DefaultLoadingComponent = ({ message }: LoadingComponentProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <div>Loading{message ? `: ${message}` : '...'}</div> </div>
); export function withLoading<P extends WithLoadingProps>( Component: ComponentType<P>, LoadingComponent: ComponentType<LoadingComponentProps> = DefaultLoadingComponent, loadingMessage?: string
) { return function WithLoadingComponent(props: P) { if (props.isLoading) { return <LoadingComponent message={loadingMessage} />; } return <Component {...props} />; };
} COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react'; interface AnalyticsEvent { eventName: string; properties?: Record<string, unknown>;
} const analytics = { track: (event: AnalyticsEvent) => { console.log('Analytics event:', event); // En producción, esto enviaría datos a tu servicio de analytics },
}; export function withAnalytics<P extends object>( Component: ComponentType<P>, eventName: string, getProperties?: (props: P) => Record<string, unknown>
) { return function AnalyticsComponent(props: P) { useEffect(() => { analytics.track({ eventName: `${eventName}_viewed`, properties: getProperties?.(props), }); }, []); const handleClick = () => { analytics.track({ eventName: `${eventName}_clicked`, properties: getProperties?.(props), }); }; return ( <div onClick={handleClick}> <Component {...props} /> </div> ); };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react'; interface AnalyticsEvent { eventName: string; properties?: Record<string, unknown>;
} const analytics = { track: (event: AnalyticsEvent) => { console.log('Analytics event:', event); // En producción, esto enviaría datos a tu servicio de analytics },
}; export function withAnalytics<P extends object>( Component: ComponentType<P>, eventName: string, getProperties?: (props: P) => Record<string, unknown>
) { return function AnalyticsComponent(props: P) { useEffect(() => { analytics.track({ eventName: `${eventName}_viewed`, properties: getProperties?.(props), }); }, []); const handleClick = () => { analytics.track({ eventName: `${eventName}_clicked`, properties: getProperties?.(props), }); }; return ( <div onClick={handleClick}> <Component {...props} /> </div> ); };
} COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react'; interface AnalyticsEvent { eventName: string; properties?: Record<string, unknown>;
} const analytics = { track: (event: AnalyticsEvent) => { console.log('Analytics event:', event); // En producción, esto enviaría datos a tu servicio de analytics },
}; export function withAnalytics<P extends object>( Component: ComponentType<P>, eventName: string, getProperties?: (props: P) => Record<string, unknown>
) { return function AnalyticsComponent(props: P) { useEffect(() => { analytics.track({ eventName: `${eventName}_viewed`, properties: getProperties?.(props), }); }, []); const handleClick = () => { analytics.track({ eventName: `${eventName}_clicked`, properties: getProperties?.(props), }); }; return ( <div onClick={handleClick}> <Component {...props} /> </div> ); };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; export function compose<P>(...hocs: Array<(component: ComponentType<any>) => ComponentType<any>>) { return (component: ComponentType<P>) => { return hocs.reduceRight((acc, hoc) => hoc(acc), component); };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType } from 'react'; export function compose<P>(...hocs: Array<(component: ComponentType<any>) => ComponentType<any>>) { return (component: ComponentType<P>) => { return hocs.reduceRight((acc, hoc) => hoc(acc), component); };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; export function compose<P>(...hocs: Array<(component: ComponentType<any>) => ComponentType<any>>) { return (component: ComponentType<P>) => { return hocs.reduceRight((acc, hoc) => hoc(acc), component); };
} COMMAND_BLOCK:
import { withAuth } from './withAuth';
import { withErrorBoundary } from './withErrorBoundary';
import { withLoading } from './withLoading';
import { withAnalytics } from './withAnalytics';
import { compose } from './compose'; interface DashboardProps { user: { id: string; name: string; email: string; }; isLoading: boolean;
} function Dashboard({ user, isLoading }: DashboardProps) { return ( <div> <h1>Dashboard</h1> <p>Welcome, {user.name}</p> </div> );
} // Aplicar un solo HOC
export const ProtectedDashboard = withAuth(Dashboard, { requiredRoles: ['admin'],
}); // Aplicar múltiples HOCs manualmente
export const EnhancedDashboard = withErrorBoundary( withAuth( withLoading(Dashboard), { requiredRoles: ['admin'] } )
); // Usar compose para aplicar múltiples HOCs de manera más legible
export const ComposedDashboard = compose<DashboardProps>( withErrorBoundary, (Component) => withAuth(Component, { requiredRoles: ['admin'] }), withLoading, (Component) => withAnalytics(Component, 'dashboard')
)(Dashboard); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { withAuth } from './withAuth';
import { withErrorBoundary } from './withErrorBoundary';
import { withLoading } from './withLoading';
import { withAnalytics } from './withAnalytics';
import { compose } from './compose'; interface DashboardProps { user: { id: string; name: string; email: string; }; isLoading: boolean;
} function Dashboard({ user, isLoading }: DashboardProps) { return ( <div> <h1>Dashboard</h1> <p>Welcome, {user.name}</p> </div> );
} // Aplicar un solo HOC
export const ProtectedDashboard = withAuth(Dashboard, { requiredRoles: ['admin'],
}); // Aplicar múltiples HOCs manualmente
export const EnhancedDashboard = withErrorBoundary( withAuth( withLoading(Dashboard), { requiredRoles: ['admin'] } )
); // Usar compose para aplicar múltiples HOCs de manera más legible
export const ComposedDashboard = compose<DashboardProps>( withErrorBoundary, (Component) => withAuth(Component, { requiredRoles: ['admin'] }), withLoading, (Component) => withAnalytics(Component, 'dashboard')
)(Dashboard); COMMAND_BLOCK:
import { withAuth } from './withAuth';
import { withErrorBoundary } from './withErrorBoundary';
import { withLoading } from './withLoading';
import { withAnalytics } from './withAnalytics';
import { compose } from './compose'; interface DashboardProps { user: { id: string; name: string; email: string; }; isLoading: boolean;
} function Dashboard({ user, isLoading }: DashboardProps) { return ( <div> <h1>Dashboard</h1> <p>Welcome, {user.name}</p> </div> );
} // Aplicar un solo HOC
export const ProtectedDashboard = withAuth(Dashboard, { requiredRoles: ['admin'],
}); // Aplicar múltiples HOCs manualmente
export const EnhancedDashboard = withErrorBoundary( withAuth( withLoading(Dashboard), { requiredRoles: ['admin'] } )
); // Usar compose para aplicar múltiples HOCs de manera más legible
export const ComposedDashboard = compose<DashboardProps>( withErrorBoundary, (Component) => withAuth(Component, { requiredRoles: ['admin'] }), withLoading, (Component) => withAnalytics(Component, 'dashboard')
)(Dashboard); COMMAND_BLOCK:
import { connect } from 'react-redux'; interface StateProps { todos: Todo[]; filter: string;
} interface DispatchProps { addTodo: (text: string) => void; toggleTodo: (id: string) => void;
} interface OwnProps { userId: string;
} type TodoListProps = StateProps & DispatchProps & OwnProps; function TodoList({ todos, filter, addTodo, toggleTodo }: TodoListProps) { return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => toggleTodo(todo.id)}> {todo.text} </li> ))} </ul> );
} const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => ({ todos: state.todos.filter((t) => t.userId === ownProps.userId), filter: state.filter,
}); const mapDispatchToProps: DispatchProps = { addTodo, toggleTodo,
}; export default connect(mapStateToProps, mapDispatchToProps)(TodoList); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { connect } from 'react-redux'; interface StateProps { todos: Todo[]; filter: string;
} interface DispatchProps { addTodo: (text: string) => void; toggleTodo: (id: string) => void;
} interface OwnProps { userId: string;
} type TodoListProps = StateProps & DispatchProps & OwnProps; function TodoList({ todos, filter, addTodo, toggleTodo }: TodoListProps) { return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => toggleTodo(todo.id)}> {todo.text} </li> ))} </ul> );
} const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => ({ todos: state.todos.filter((t) => t.userId === ownProps.userId), filter: state.filter,
}); const mapDispatchToProps: DispatchProps = { addTodo, toggleTodo,
}; export default connect(mapStateToProps, mapDispatchToProps)(TodoList); COMMAND_BLOCK:
import { connect } from 'react-redux'; interface StateProps { todos: Todo[]; filter: string;
} interface DispatchProps { addTodo: (text: string) => void; toggleTodo: (id: string) => void;
} interface OwnProps { userId: string;
} type TodoListProps = StateProps & DispatchProps & OwnProps; function TodoList({ todos, filter, addTodo, toggleTodo }: TodoListProps) { return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => toggleTodo(todo.id)}> {todo.text} </li> ))} </ul> );
} const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => ({ todos: state.todos.filter((t) => t.userId === ownProps.userId), filter: state.filter,
}); const mapDispatchToProps: DispatchProps = { addTodo, toggleTodo,
}; export default connect(mapStateToProps, mapDispatchToProps)(TodoList); COMMAND_BLOCK:
import { useSelector, useDispatch } from 'react-redux'; interface TodoListProps { userId: string;
} function TodoList({ userId }: TodoListProps) { const todos = useSelector((state: RootState) => state.todos.filter((t) => t.userId === userId) ); const filter = useSelector((state: RootState) => state.filter); const dispatch = useDispatch(); return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => dispatch(toggleTodo(todo.id))}> {todo.text} </li> ))} </ul> );
} export default TodoList; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useSelector, useDispatch } from 'react-redux'; interface TodoListProps { userId: string;
} function TodoList({ userId }: TodoListProps) { const todos = useSelector((state: RootState) => state.todos.filter((t) => t.userId === userId) ); const filter = useSelector((state: RootState) => state.filter); const dispatch = useDispatch(); return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => dispatch(toggleTodo(todo.id))}> {todo.text} </li> ))} </ul> );
} export default TodoList; COMMAND_BLOCK:
import { useSelector, useDispatch } from 'react-redux'; interface TodoListProps { userId: string;
} function TodoList({ userId }: TodoListProps) { const todos = useSelector((state: RootState) => state.todos.filter((t) => t.userId === userId) ); const filter = useSelector((state: RootState) => state.filter); const dispatch = useDispatch(); return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => dispatch(toggleTodo(todo.id))}> {todo.text} </li> ))} </ul> );
} export default TodoList; COMMAND_BLOCK:
import { withRouter, RouteComponentProps } from 'react-router-dom'; interface ProductDetailProps extends RouteComponentProps<{ id: string }> { // props adicionales
} function ProductDetail({ match, history, location }: ProductDetailProps) { const productId = match.params.id; const handleBack = () => { history.push('/products'); }; return ( <div> <h1>Product {productId}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default withRouter(ProductDetail); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { withRouter, RouteComponentProps } from 'react-router-dom'; interface ProductDetailProps extends RouteComponentProps<{ id: string }> { // props adicionales
} function ProductDetail({ match, history, location }: ProductDetailProps) { const productId = match.params.id; const handleBack = () => { history.push('/products'); }; return ( <div> <h1>Product {productId}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default withRouter(ProductDetail); COMMAND_BLOCK:
import { withRouter, RouteComponentProps } from 'react-router-dom'; interface ProductDetailProps extends RouteComponentProps<{ id: string }> { // props adicionales
} function ProductDetail({ match, history, location }: ProductDetailProps) { const productId = match.params.id; const handleBack = () => { history.push('/products'); }; return ( <div> <h1>Product {productId}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default withRouter(ProductDetail); COMMAND_BLOCK:
import { useParams, useNavigate, useLocation } from 'react-router-dom'; function ProductDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const location = useLocation(); const handleBack = () => { navigate('/products'); }; return ( <div> <h1>Product {id}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default ProductDetail; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useParams, useNavigate, useLocation } from 'react-router-dom'; function ProductDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const location = useLocation(); const handleBack = () => { navigate('/products'); }; return ( <div> <h1>Product {id}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default ProductDetail; COMMAND_BLOCK:
import { useParams, useNavigate, useLocation } from 'react-router-dom'; function ProductDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const location = useLocation(); const handleBack = () => { navigate('/products'); }; return ( <div> <h1>Product {id}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default ProductDetail; COMMAND_BLOCK:
import { graphql, ChildProps } from '@apollo/react-hoc';
import gql from 'graphql-tag'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface Response { users: User[];
} type ChildComponentProps = ChildProps<{}, Response>; function UserList({ data }: ChildComponentProps) { if (data?.loading) return <div>Loading...</div>; if (data?.error) return <div>Error: {data.error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default graphql<{}, Response>(GET_USERS)(UserList); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { graphql, ChildProps } from '@apollo/react-hoc';
import gql from 'graphql-tag'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface Response { users: User[];
} type ChildComponentProps = ChildProps<{}, Response>; function UserList({ data }: ChildComponentProps) { if (data?.loading) return <div>Loading...</div>; if (data?.error) return <div>Error: {data.error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default graphql<{}, Response>(GET_USERS)(UserList); COMMAND_BLOCK:
import { graphql, ChildProps } from '@apollo/react-hoc';
import gql from 'graphql-tag'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface Response { users: User[];
} type ChildComponentProps = ChildProps<{}, Response>; function UserList({ data }: ChildComponentProps) { if (data?.loading) return <div>Loading...</div>; if (data?.error) return <div>Error: {data.error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default graphql<{}, Response>(GET_USERS)(UserList); COMMAND_BLOCK:
import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface GetUsersData { users: User[];
} function UserList() { const { data, loading, error } = useQuery<GetUsersData>(GET_USERS); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default UserList; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface GetUsersData { users: User[];
} function UserList() { const { data, loading, error } = useQuery<GetUsersData>(GET_USERS); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default UserList; COMMAND_BLOCK:
import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface GetUsersData { users: User[];
} function UserList() { const { data, loading, error } = useQuery<GetUsersData>(GET_USERS); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default UserList; COMMAND_BLOCK:
import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core'; const styles = (theme: Theme) => createStyles({ root: { padding: theme.spacing(2), backgroundColor: theme.palette.primary.main, }, title: { color: theme.palette.primary.contrastText, }, }); interface CardProps extends WithStyles<typeof styles> { title: string;
} function Card({ classes, title }: CardProps) { return ( <div className={classes.root}> <h2 className={classes.title}>{title}</h2> </div> );
} export default withStyles(styles)(Card); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core'; const styles = (theme: Theme) => createStyles({ root: { padding: theme.spacing(2), backgroundColor: theme.palette.primary.main, }, title: { color: theme.palette.primary.contrastText, }, }); interface CardProps extends WithStyles<typeof styles> { title: string;
} function Card({ classes, title }: CardProps) { return ( <div className={classes.root}> <h2 className={classes.title}>{title}</h2> </div> );
} export default withStyles(styles)(Card); COMMAND_BLOCK:
import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core'; const styles = (theme: Theme) => createStyles({ root: { padding: theme.spacing(2), backgroundColor: theme.palette.primary.main, }, title: { color: theme.palette.primary.contrastText, }, }); interface CardProps extends WithStyles<typeof styles> { title: string;
} function Card({ classes, title }: CardProps) { return ( <div className={classes.root}> <h2 className={classes.title}>{title}</h2> </div> );
} export default withStyles(styles)(Card); CODE_BLOCK:
import { Box, Typography, useTheme } from '@mui/material'; interface CardProps { title: string;
} function Card({ title }: CardProps) { const theme = useTheme(); return ( <Box sx={{ padding: 2, backgroundColor: 'primary.main', }} > <Typography variant="h5" sx={{ color: 'primary.contrastText', }} > {title} </Typography> </Box> );
} export default Card; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Box, Typography, useTheme } from '@mui/material'; interface CardProps { title: string;
} function Card({ title }: CardProps) { const theme = useTheme(); return ( <Box sx={{ padding: 2, backgroundColor: 'primary.main', }} > <Typography variant="h5" sx={{ color: 'primary.contrastText', }} > {title} </Typography> </Box> );
} export default Card; CODE_BLOCK:
import { Box, Typography, useTheme } from '@mui/material'; interface CardProps { title: string;
} function Card({ title }: CardProps) { const theme = useTheme(); return ( <Box sx={{ padding: 2, backgroundColor: 'primary.main', }} > <Typography variant="h5" sx={{ color: 'primary.contrastText', }} > {title} </Typography> </Box> );
} export default Card; CODE_BLOCK:
function withSomething(Component: ComponentType): ComponentType Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
function withSomething(Component: ComponentType): ComponentType CODE_BLOCK:
function withSomething(Component: ComponentType): ComponentType COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; interface AuthUser { id: string; name: string; email: string; roles: string[];
} interface WithAuthProps { user: AuthUser;
} interface WithAuthOptions { redirectTo?: string; requiredRoles?: string[]; LoadingComponent?: ComponentType;
} function useAuth() { // Simulación de hook de autenticación // En una app real, esto vendría de tu sistema de auth const user: AuthUser | null = { id: '1', name: 'John Doe', email: '[email protected]', roles: ['user'], }; const loading = false; return { user, loading };
} export function withAuth<P extends WithAuthProps>( Component: ComponentType<P>, options: WithAuthOptions = {}
) { const { redirectTo = '/login', requiredRoles = [], LoadingComponent = () => <div>Loading...</div>, } = options; return function AuthenticatedComponent( props: Omit<P, keyof WithAuthProps> ) { const { user, loading } = useAuth(); const navigate = useNavigate(); useEffect(() => { if (!loading && !user) { navigate(redirectTo); return; } if (user && requiredRoles.length > 0) { const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role) ); if (!hasRequiredRole) { navigate('/unauthorized'); } } }, [user, loading, navigate]); if (loading) { return <LoadingComponent />; } if (!user) { return null; } return <Component {...(props as P)} user={user} />; };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; interface AuthUser { id: string; name: string; email: string; roles: string[];
} interface WithAuthProps { user: AuthUser;
} interface WithAuthOptions { redirectTo?: string; requiredRoles?: string[]; LoadingComponent?: ComponentType;
} function useAuth() { // Simulación de hook de autenticación // En una app real, esto vendría de tu sistema de auth const user: AuthUser | null = { id: '1', name: 'John Doe', email: '[email protected]', roles: ['user'], }; const loading = false; return { user, loading };
} export function withAuth<P extends WithAuthProps>( Component: ComponentType<P>, options: WithAuthOptions = {}
) { const { redirectTo = '/login', requiredRoles = [], LoadingComponent = () => <div>Loading...</div>, } = options; return function AuthenticatedComponent( props: Omit<P, keyof WithAuthProps> ) { const { user, loading } = useAuth(); const navigate = useNavigate(); useEffect(() => { if (!loading && !user) { navigate(redirectTo); return; } if (user && requiredRoles.length > 0) { const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role) ); if (!hasRequiredRole) { navigate('/unauthorized'); } } }, [user, loading, navigate]); if (loading) { return <LoadingComponent />; } if (!user) { return null; } return <Component {...(props as P)} user={user} />; };
} COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; interface AuthUser { id: string; name: string; email: string; roles: string[];
} interface WithAuthProps { user: AuthUser;
} interface WithAuthOptions { redirectTo?: string; requiredRoles?: string[]; LoadingComponent?: ComponentType;
} function useAuth() { // Simulación de hook de autenticación // En una app real, esto vendría de tu sistema de auth const user: AuthUser | null = { id: '1', name: 'John Doe', email: '[email protected]', roles: ['user'], }; const loading = false; return { user, loading };
} export function withAuth<P extends WithAuthProps>( Component: ComponentType<P>, options: WithAuthOptions = {}
) { const { redirectTo = '/login', requiredRoles = [], LoadingComponent = () => <div>Loading...</div>, } = options; return function AuthenticatedComponent( props: Omit<P, keyof WithAuthProps> ) { const { user, loading } = useAuth(); const navigate = useNavigate(); useEffect(() => { if (!loading && !user) { navigate(redirectTo); return; } if (user && requiredRoles.length > 0) { const hasRequiredRole = requiredRoles.some((role) => user.roles.includes(role) ); if (!hasRequiredRole) { navigate('/unauthorized'); } } }, [user, loading, navigate]); if (loading) { return <LoadingComponent />; } if (!user) { return null; } return <Component {...(props as P)} user={user} />; };
} COMMAND_BLOCK:
import { Component, ComponentType, ErrorInfo, ReactNode } from 'react'; interface ErrorBoundaryState { hasError: boolean; error: Error | null;
} interface ErrorFallbackProps { error: Error | null; resetError: () => void;
} const DefaultErrorFallback = ({ error, resetError }: ErrorFallbackProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <h2>Something went wrong</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {error?.message} </details> <button onClick={resetError}>Try again</button> </div>
); export function withErrorBoundary<P extends object>( WrappedComponent: ComponentType<P>, ErrorFallback: ComponentType<ErrorFallbackProps> = DefaultErrorFallback
) { return class WithErrorBoundary extends Component<P, ErrorBoundaryState> { constructor(props: P) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('Error caught by boundary:', error, errorInfo); // Aquí podrías enviar el error a un servicio de logging // logErrorToService(error, errorInfo); } resetError = (): void => { this.setState({ hasError: false, error: null }); }; render(): ReactNode { if (this.state.hasError) { return ( <ErrorFallback error={this.state.error} resetError={this.resetError} /> ); } return <WrappedComponent {...this.props} />; } };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Component, ComponentType, ErrorInfo, ReactNode } from 'react'; interface ErrorBoundaryState { hasError: boolean; error: Error | null;
} interface ErrorFallbackProps { error: Error | null; resetError: () => void;
} const DefaultErrorFallback = ({ error, resetError }: ErrorFallbackProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <h2>Something went wrong</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {error?.message} </details> <button onClick={resetError}>Try again</button> </div>
); export function withErrorBoundary<P extends object>( WrappedComponent: ComponentType<P>, ErrorFallback: ComponentType<ErrorFallbackProps> = DefaultErrorFallback
) { return class WithErrorBoundary extends Component<P, ErrorBoundaryState> { constructor(props: P) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('Error caught by boundary:', error, errorInfo); // Aquí podrías enviar el error a un servicio de logging // logErrorToService(error, errorInfo); } resetError = (): void => { this.setState({ hasError: false, error: null }); }; render(): ReactNode { if (this.state.hasError) { return ( <ErrorFallback error={this.state.error} resetError={this.resetError} /> ); } return <WrappedComponent {...this.props} />; } };
} COMMAND_BLOCK:
import { Component, ComponentType, ErrorInfo, ReactNode } from 'react'; interface ErrorBoundaryState { hasError: boolean; error: Error | null;
} interface ErrorFallbackProps { error: Error | null; resetError: () => void;
} const DefaultErrorFallback = ({ error, resetError }: ErrorFallbackProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <h2>Something went wrong</h2> <details style={{ whiteSpace: 'pre-wrap' }}> {error?.message} </details> <button onClick={resetError}>Try again</button> </div>
); export function withErrorBoundary<P extends object>( WrappedComponent: ComponentType<P>, ErrorFallback: ComponentType<ErrorFallbackProps> = DefaultErrorFallback
) { return class WithErrorBoundary extends Component<P, ErrorBoundaryState> { constructor(props: P) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { console.error('Error caught by boundary:', error, errorInfo); // Aquí podrías enviar el error a un servicio de logging // logErrorToService(error, errorInfo); } resetError = (): void => { this.setState({ hasError: false, error: null }); }; render(): ReactNode { if (this.state.hasError) { return ( <ErrorFallback error={this.state.error} resetError={this.resetError} /> ); } return <WrappedComponent {...this.props} />; } };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; interface WithLoadingProps { isLoading: boolean;
} interface LoadingComponentProps { message?: string;
} const DefaultLoadingComponent = ({ message }: LoadingComponentProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <div>Loading{message ? `: ${message}` : '...'}</div> </div>
); export function withLoading<P extends WithLoadingProps>( Component: ComponentType<P>, LoadingComponent: ComponentType<LoadingComponentProps> = DefaultLoadingComponent, loadingMessage?: string
) { return function WithLoadingComponent(props: P) { if (props.isLoading) { return <LoadingComponent message={loadingMessage} />; } return <Component {...props} />; };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType } from 'react'; interface WithLoadingProps { isLoading: boolean;
} interface LoadingComponentProps { message?: string;
} const DefaultLoadingComponent = ({ message }: LoadingComponentProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <div>Loading{message ? `: ${message}` : '...'}</div> </div>
); export function withLoading<P extends WithLoadingProps>( Component: ComponentType<P>, LoadingComponent: ComponentType<LoadingComponentProps> = DefaultLoadingComponent, loadingMessage?: string
) { return function WithLoadingComponent(props: P) { if (props.isLoading) { return <LoadingComponent message={loadingMessage} />; } return <Component {...props} />; };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; interface WithLoadingProps { isLoading: boolean;
} interface LoadingComponentProps { message?: string;
} const DefaultLoadingComponent = ({ message }: LoadingComponentProps) => ( <div style={{ padding: '20px', textAlign: 'center' }}> <div>Loading{message ? `: ${message}` : '...'}</div> </div>
); export function withLoading<P extends WithLoadingProps>( Component: ComponentType<P>, LoadingComponent: ComponentType<LoadingComponentProps> = DefaultLoadingComponent, loadingMessage?: string
) { return function WithLoadingComponent(props: P) { if (props.isLoading) { return <LoadingComponent message={loadingMessage} />; } return <Component {...props} />; };
} COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react'; interface AnalyticsEvent { eventName: string; properties?: Record<string, unknown>;
} const analytics = { track: (event: AnalyticsEvent) => { console.log('Analytics event:', event); // En producción, esto enviaría datos a tu servicio de analytics },
}; export function withAnalytics<P extends object>( Component: ComponentType<P>, eventName: string, getProperties?: (props: P) => Record<string, unknown>
) { return function AnalyticsComponent(props: P) { useEffect(() => { analytics.track({ eventName: `${eventName}_viewed`, properties: getProperties?.(props), }); }, []); const handleClick = () => { analytics.track({ eventName: `${eventName}_clicked`, properties: getProperties?.(props), }); }; return ( <div onClick={handleClick}> <Component {...props} /> </div> ); };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react'; interface AnalyticsEvent { eventName: string; properties?: Record<string, unknown>;
} const analytics = { track: (event: AnalyticsEvent) => { console.log('Analytics event:', event); // En producción, esto enviaría datos a tu servicio de analytics },
}; export function withAnalytics<P extends object>( Component: ComponentType<P>, eventName: string, getProperties?: (props: P) => Record<string, unknown>
) { return function AnalyticsComponent(props: P) { useEffect(() => { analytics.track({ eventName: `${eventName}_viewed`, properties: getProperties?.(props), }); }, []); const handleClick = () => { analytics.track({ eventName: `${eventName}_clicked`, properties: getProperties?.(props), }); }; return ( <div onClick={handleClick}> <Component {...props} /> </div> ); };
} COMMAND_BLOCK:
import { ComponentType, useEffect } from 'react'; interface AnalyticsEvent { eventName: string; properties?: Record<string, unknown>;
} const analytics = { track: (event: AnalyticsEvent) => { console.log('Analytics event:', event); // En producción, esto enviaría datos a tu servicio de analytics },
}; export function withAnalytics<P extends object>( Component: ComponentType<P>, eventName: string, getProperties?: (props: P) => Record<string, unknown>
) { return function AnalyticsComponent(props: P) { useEffect(() => { analytics.track({ eventName: `${eventName}_viewed`, properties: getProperties?.(props), }); }, []); const handleClick = () => { analytics.track({ eventName: `${eventName}_clicked`, properties: getProperties?.(props), }); }; return ( <div onClick={handleClick}> <Component {...props} /> </div> ); };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; export function compose<P>(...hocs: Array<(component: ComponentType<any>) => ComponentType<any>>) { return (component: ComponentType<P>) => { return hocs.reduceRight((acc, hoc) => hoc(acc), component); };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { ComponentType } from 'react'; export function compose<P>(...hocs: Array<(component: ComponentType<any>) => ComponentType<any>>) { return (component: ComponentType<P>) => { return hocs.reduceRight((acc, hoc) => hoc(acc), component); };
} COMMAND_BLOCK:
import { ComponentType } from 'react'; export function compose<P>(...hocs: Array<(component: ComponentType<any>) => ComponentType<any>>) { return (component: ComponentType<P>) => { return hocs.reduceRight((acc, hoc) => hoc(acc), component); };
} COMMAND_BLOCK:
import { withAuth } from './withAuth';
import { withErrorBoundary } from './withErrorBoundary';
import { withLoading } from './withLoading';
import { withAnalytics } from './withAnalytics';
import { compose } from './compose'; interface DashboardProps { user: { id: string; name: string; email: string; }; isLoading: boolean;
} function Dashboard({ user, isLoading }: DashboardProps) { return ( <div> <h1>Dashboard</h1> <p>Welcome, {user.name}</p> </div> );
} // Aplicar un solo HOC
export const ProtectedDashboard = withAuth(Dashboard, { requiredRoles: ['admin'],
}); // Aplicar múltiples HOCs manualmente
export const EnhancedDashboard = withErrorBoundary( withAuth( withLoading(Dashboard), { requiredRoles: ['admin'] } )
); // Usar compose para aplicar múltiples HOCs de manera más legible
export const ComposedDashboard = compose<DashboardProps>( withErrorBoundary, (Component) => withAuth(Component, { requiredRoles: ['admin'] }), withLoading, (Component) => withAnalytics(Component, 'dashboard')
)(Dashboard); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { withAuth } from './withAuth';
import { withErrorBoundary } from './withErrorBoundary';
import { withLoading } from './withLoading';
import { withAnalytics } from './withAnalytics';
import { compose } from './compose'; interface DashboardProps { user: { id: string; name: string; email: string; }; isLoading: boolean;
} function Dashboard({ user, isLoading }: DashboardProps) { return ( <div> <h1>Dashboard</h1> <p>Welcome, {user.name}</p> </div> );
} // Aplicar un solo HOC
export const ProtectedDashboard = withAuth(Dashboard, { requiredRoles: ['admin'],
}); // Aplicar múltiples HOCs manualmente
export const EnhancedDashboard = withErrorBoundary( withAuth( withLoading(Dashboard), { requiredRoles: ['admin'] } )
); // Usar compose para aplicar múltiples HOCs de manera más legible
export const ComposedDashboard = compose<DashboardProps>( withErrorBoundary, (Component) => withAuth(Component, { requiredRoles: ['admin'] }), withLoading, (Component) => withAnalytics(Component, 'dashboard')
)(Dashboard); COMMAND_BLOCK:
import { withAuth } from './withAuth';
import { withErrorBoundary } from './withErrorBoundary';
import { withLoading } from './withLoading';
import { withAnalytics } from './withAnalytics';
import { compose } from './compose'; interface DashboardProps { user: { id: string; name: string; email: string; }; isLoading: boolean;
} function Dashboard({ user, isLoading }: DashboardProps) { return ( <div> <h1>Dashboard</h1> <p>Welcome, {user.name}</p> </div> );
} // Aplicar un solo HOC
export const ProtectedDashboard = withAuth(Dashboard, { requiredRoles: ['admin'],
}); // Aplicar múltiples HOCs manualmente
export const EnhancedDashboard = withErrorBoundary( withAuth( withLoading(Dashboard), { requiredRoles: ['admin'] } )
); // Usar compose para aplicar múltiples HOCs de manera más legible
export const ComposedDashboard = compose<DashboardProps>( withErrorBoundary, (Component) => withAuth(Component, { requiredRoles: ['admin'] }), withLoading, (Component) => withAnalytics(Component, 'dashboard')
)(Dashboard); COMMAND_BLOCK:
import { connect } from 'react-redux'; interface StateProps { todos: Todo[]; filter: string;
} interface DispatchProps { addTodo: (text: string) => void; toggleTodo: (id: string) => void;
} interface OwnProps { userId: string;
} type TodoListProps = StateProps & DispatchProps & OwnProps; function TodoList({ todos, filter, addTodo, toggleTodo }: TodoListProps) { return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => toggleTodo(todo.id)}> {todo.text} </li> ))} </ul> );
} const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => ({ todos: state.todos.filter((t) => t.userId === ownProps.userId), filter: state.filter,
}); const mapDispatchToProps: DispatchProps = { addTodo, toggleTodo,
}; export default connect(mapStateToProps, mapDispatchToProps)(TodoList); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { connect } from 'react-redux'; interface StateProps { todos: Todo[]; filter: string;
} interface DispatchProps { addTodo: (text: string) => void; toggleTodo: (id: string) => void;
} interface OwnProps { userId: string;
} type TodoListProps = StateProps & DispatchProps & OwnProps; function TodoList({ todos, filter, addTodo, toggleTodo }: TodoListProps) { return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => toggleTodo(todo.id)}> {todo.text} </li> ))} </ul> );
} const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => ({ todos: state.todos.filter((t) => t.userId === ownProps.userId), filter: state.filter,
}); const mapDispatchToProps: DispatchProps = { addTodo, toggleTodo,
}; export default connect(mapStateToProps, mapDispatchToProps)(TodoList); COMMAND_BLOCK:
import { connect } from 'react-redux'; interface StateProps { todos: Todo[]; filter: string;
} interface DispatchProps { addTodo: (text: string) => void; toggleTodo: (id: string) => void;
} interface OwnProps { userId: string;
} type TodoListProps = StateProps & DispatchProps & OwnProps; function TodoList({ todos, filter, addTodo, toggleTodo }: TodoListProps) { return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => toggleTodo(todo.id)}> {todo.text} </li> ))} </ul> );
} const mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => ({ todos: state.todos.filter((t) => t.userId === ownProps.userId), filter: state.filter,
}); const mapDispatchToProps: DispatchProps = { addTodo, toggleTodo,
}; export default connect(mapStateToProps, mapDispatchToProps)(TodoList); COMMAND_BLOCK:
import { useSelector, useDispatch } from 'react-redux'; interface TodoListProps { userId: string;
} function TodoList({ userId }: TodoListProps) { const todos = useSelector((state: RootState) => state.todos.filter((t) => t.userId === userId) ); const filter = useSelector((state: RootState) => state.filter); const dispatch = useDispatch(); return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => dispatch(toggleTodo(todo.id))}> {todo.text} </li> ))} </ul> );
} export default TodoList; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useSelector, useDispatch } from 'react-redux'; interface TodoListProps { userId: string;
} function TodoList({ userId }: TodoListProps) { const todos = useSelector((state: RootState) => state.todos.filter((t) => t.userId === userId) ); const filter = useSelector((state: RootState) => state.filter); const dispatch = useDispatch(); return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => dispatch(toggleTodo(todo.id))}> {todo.text} </li> ))} </ul> );
} export default TodoList; COMMAND_BLOCK:
import { useSelector, useDispatch } from 'react-redux'; interface TodoListProps { userId: string;
} function TodoList({ userId }: TodoListProps) { const todos = useSelector((state: RootState) => state.todos.filter((t) => t.userId === userId) ); const filter = useSelector((state: RootState) => state.filter); const dispatch = useDispatch(); return ( <ul> {todos.map((todo) => ( <li key={todo.id} onClick={() => dispatch(toggleTodo(todo.id))}> {todo.text} </li> ))} </ul> );
} export default TodoList; COMMAND_BLOCK:
import { withRouter, RouteComponentProps } from 'react-router-dom'; interface ProductDetailProps extends RouteComponentProps<{ id: string }> { // props adicionales
} function ProductDetail({ match, history, location }: ProductDetailProps) { const productId = match.params.id; const handleBack = () => { history.push('/products'); }; return ( <div> <h1>Product {productId}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default withRouter(ProductDetail); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { withRouter, RouteComponentProps } from 'react-router-dom'; interface ProductDetailProps extends RouteComponentProps<{ id: string }> { // props adicionales
} function ProductDetail({ match, history, location }: ProductDetailProps) { const productId = match.params.id; const handleBack = () => { history.push('/products'); }; return ( <div> <h1>Product {productId}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default withRouter(ProductDetail); COMMAND_BLOCK:
import { withRouter, RouteComponentProps } from 'react-router-dom'; interface ProductDetailProps extends RouteComponentProps<{ id: string }> { // props adicionales
} function ProductDetail({ match, history, location }: ProductDetailProps) { const productId = match.params.id; const handleBack = () => { history.push('/products'); }; return ( <div> <h1>Product {productId}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default withRouter(ProductDetail); COMMAND_BLOCK:
import { useParams, useNavigate, useLocation } from 'react-router-dom'; function ProductDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const location = useLocation(); const handleBack = () => { navigate('/products'); }; return ( <div> <h1>Product {id}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default ProductDetail; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useParams, useNavigate, useLocation } from 'react-router-dom'; function ProductDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const location = useLocation(); const handleBack = () => { navigate('/products'); }; return ( <div> <h1>Product {id}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default ProductDetail; COMMAND_BLOCK:
import { useParams, useNavigate, useLocation } from 'react-router-dom'; function ProductDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const location = useLocation(); const handleBack = () => { navigate('/products'); }; return ( <div> <h1>Product {id}</h1> <button onClick={handleBack}>Back to products</button> </div> );
} export default ProductDetail; COMMAND_BLOCK:
import { graphql, ChildProps } from '@apollo/react-hoc';
import gql from 'graphql-tag'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface Response { users: User[];
} type ChildComponentProps = ChildProps<{}, Response>; function UserList({ data }: ChildComponentProps) { if (data?.loading) return <div>Loading...</div>; if (data?.error) return <div>Error: {data.error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default graphql<{}, Response>(GET_USERS)(UserList); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { graphql, ChildProps } from '@apollo/react-hoc';
import gql from 'graphql-tag'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface Response { users: User[];
} type ChildComponentProps = ChildProps<{}, Response>; function UserList({ data }: ChildComponentProps) { if (data?.loading) return <div>Loading...</div>; if (data?.error) return <div>Error: {data.error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default graphql<{}, Response>(GET_USERS)(UserList); COMMAND_BLOCK:
import { graphql, ChildProps } from '@apollo/react-hoc';
import gql from 'graphql-tag'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface Response { users: User[];
} type ChildComponentProps = ChildProps<{}, Response>; function UserList({ data }: ChildComponentProps) { if (data?.loading) return <div>Loading...</div>; if (data?.error) return <div>Error: {data.error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default graphql<{}, Response>(GET_USERS)(UserList); COMMAND_BLOCK:
import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface GetUsersData { users: User[];
} function UserList() { const { data, loading, error } = useQuery<GetUsersData>(GET_USERS); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default UserList; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface GetUsersData { users: User[];
} function UserList() { const { data, loading, error } = useQuery<GetUsersData>(GET_USERS); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default UserList; COMMAND_BLOCK:
import { useQuery, gql } from '@apollo/client'; const GET_USERS = gql` query GetUsers { users { id name email } }
`; interface User { id: string; name: string; email: string;
} interface GetUsersData { users: User[];
} function UserList() { const { data, loading, error } = useQuery<GetUsersData>(GET_USERS); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data?.users?.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> );
} export default UserList; COMMAND_BLOCK:
import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core'; const styles = (theme: Theme) => createStyles({ root: { padding: theme.spacing(2), backgroundColor: theme.palette.primary.main, }, title: { color: theme.palette.primary.contrastText, }, }); interface CardProps extends WithStyles<typeof styles> { title: string;
} function Card({ classes, title }: CardProps) { return ( <div className={classes.root}> <h2 className={classes.title}>{title}</h2> </div> );
} export default withStyles(styles)(Card); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core'; const styles = (theme: Theme) => createStyles({ root: { padding: theme.spacing(2), backgroundColor: theme.palette.primary.main, }, title: { color: theme.palette.primary.contrastText, }, }); interface CardProps extends WithStyles<typeof styles> { title: string;
} function Card({ classes, title }: CardProps) { return ( <div className={classes.root}> <h2 className={classes.title}>{title}</h2> </div> );
} export default withStyles(styles)(Card); COMMAND_BLOCK:
import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core'; const styles = (theme: Theme) => createStyles({ root: { padding: theme.spacing(2), backgroundColor: theme.palette.primary.main, }, title: { color: theme.palette.primary.contrastText, }, }); interface CardProps extends WithStyles<typeof styles> { title: string;
} function Card({ classes, title }: CardProps) { return ( <div className={classes.root}> <h2 className={classes.title}>{title}</h2> </div> );
} export default withStyles(styles)(Card); CODE_BLOCK:
import { Box, Typography, useTheme } from '@mui/material'; interface CardProps { title: string;
} function Card({ title }: CardProps) { const theme = useTheme(); return ( <Box sx={{ padding: 2, backgroundColor: 'primary.main', }} > <Typography variant="h5" sx={{ color: 'primary.contrastText', }} > {title} </Typography> </Box> );
} export default Card; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Box, Typography, useTheme } from '@mui/material'; interface CardProps { title: string;
} function Card({ title }: CardProps) { const theme = useTheme(); return ( <Box sx={{ padding: 2, backgroundColor: 'primary.main', }} > <Typography variant="h5" sx={{ color: 'primary.contrastText', }} > {title} </Typography> </Box> );
} export default Card; CODE_BLOCK:
import { Box, Typography, useTheme } from '@mui/material'; interface CardProps { title: string;
} function Card({ title }: CardProps) { const theme = useTheme(); return ( <Box sx={{ padding: 2, backgroundColor: 'primary.main', }} > <Typography variant="h5" sx={{ color: 'primary.contrastText', }} > {title} </Typography> </Box> );
} export default Card; COMMAND_BLOCK:
<DataFetcher render={(data) => <Display data={data} />} /> Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
<DataFetcher render={(data) => <Display data={data} />} /> COMMAND_BLOCK:
<DataFetcher render={(data) => <Display data={data} />} /> COMMAND_BLOCK:
<DataFetcher> {(data) => <Display data={data} />}
</DataFetcher> Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
<DataFetcher> {(data) => <Display data={data} />}
</DataFetcher> COMMAND_BLOCK:
<DataFetcher> {(data) => <Display data={data} />}
</DataFetcher> COMMAND_BLOCK:
import { useState, useEffect, ReactNode } from 'react'; interface MousePosition { x: number; y: number;
} interface MouseTrackerProps { render?: (position: MousePosition) => ReactNode; children?: (position: MousePosition) => ReactNode;
} export function MouseTracker({ render, children }: MouseTrackerProps) { const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 }); useEffect(() => { const handleMouseMove = (event: MouseEvent) => { setPosition({ x: event.clientX, y: event.clientY, }); }; window.addEventListener('mousemove', handleMouseMove); return () => { window.removeEventListener('mousemove', handleMouseMove); }; }, []); // Soporta tanto render prop como children as function const renderFunction = render || children; if (!renderFunction) { return null; } return <>{renderFunction(position)}</>;
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, useEffect, ReactNode } from 'react'; interface MousePosition { x: number; y: number;
} interface MouseTrackerProps { render?: (position: MousePosition) => ReactNode; children?: (position: MousePosition) => ReactNode;
} export function MouseTracker({ render, children }: MouseTrackerProps) { const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 }); useEffect(() => { const handleMouseMove = (event: MouseEvent) => { setPosition({ x: event.clientX, y: event.clientY, }); }; window.addEventListener('mousemove', handleMouseMove); return () => { window.removeEventListener('mousemove', handleMouseMove); }; }, []); // Soporta tanto render prop como children as function const renderFunction = render || children; if (!renderFunction) { return null; } return <>{renderFunction(position)}</>;
} COMMAND_BLOCK:
import { useState, useEffect, ReactNode } from 'react'; interface MousePosition { x: number; y: number;
} interface MouseTrackerProps { render?: (position: MousePosition) => ReactNode; children?: (position: MousePosition) => ReactNode;
} export function MouseTracker({ render, children }: MouseTrackerProps) { const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 }); useEffect(() => { const handleMouseMove = (event: MouseEvent) => { setPosition({ x: event.clientX, y: event.clientY, }); }; window.addEventListener('mousemove', handleMouseMove); return () => { window.removeEventListener('mousemove', handleMouseMove); }; }, []); // Soporta tanto render prop como children as function const renderFunction = render || children; if (!renderFunction) { return null; } return <>{renderFunction(position)}</>;
} COMMAND_BLOCK:
import { useState, useEffect, ReactNode } from 'react'; interface DataFetcherState<T> { data: T | null; loading: boolean; error: Error | null;
} interface DataFetcherProps<T> { url: string; children: (state: DataFetcherState<T>) => ReactNode;
} export function DataFetcher<T>({ url, children }: DataFetcherProps<T>) { const [state, setState] = useState<DataFetcherState<T>>({ data: null, loading: true, error: null, }); useEffect(() => { let cancelled = false; setState({ data: null, loading: true, error: null }); fetch(url) .then((response) => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then((data) => { if (!cancelled) { setState({ data, loading: false, error: null }); } }) .catch((error) => { if (!cancelled) { setState({ data: null, loading: false, error }); } }); return () => { cancelled = true; }; }, [url]); return <>{children(state)}</>;
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, useEffect, ReactNode } from 'react'; interface DataFetcherState<T> { data: T | null; loading: boolean; error: Error | null;
} interface DataFetcherProps<T> { url: string; children: (state: DataFetcherState<T>) => ReactNode;
} export function DataFetcher<T>({ url, children }: DataFetcherProps<T>) { const [state, setState] = useState<DataFetcherState<T>>({ data: null, loading: true, error: null, }); useEffect(() => { let cancelled = false; setState({ data: null, loading: true, error: null }); fetch(url) .then((response) => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then((data) => { if (!cancelled) { setState({ data, loading: false, error: null }); } }) .catch((error) => { if (!cancelled) { setState({ data: null, loading: false, error }); } }); return () => { cancelled = true; }; }, [url]); return <>{children(state)}</>;
} COMMAND_BLOCK:
import { useState, useEffect, ReactNode } from 'react'; interface DataFetcherState<T> { data: T | null; loading: boolean; error: Error | null;
} interface DataFetcherProps<T> { url: string; children: (state: DataFetcherState<T>) => ReactNode;
} export function DataFetcher<T>({ url, children }: DataFetcherProps<T>) { const [state, setState] = useState<DataFetcherState<T>>({ data: null, loading: true, error: null, }); useEffect(() => { let cancelled = false; setState({ data: null, loading: true, error: null }); fetch(url) .then((response) => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then((data) => { if (!cancelled) { setState({ data, loading: false, error: null }); } }) .catch((error) => { if (!cancelled) { setState({ data: null, loading: false, error }); } }); return () => { cancelled = true; }; }, [url]); return <>{children(state)}</>;
} COMMAND_BLOCK:
import { useState, ReactNode } from 'react'; interface ToggleState { on: boolean; toggle: () => void; setOn: () => void; setOff: () => void;
} interface ToggleProps { initial?: boolean; children: (state: ToggleState) => ReactNode;
} export function Toggle({ initial = false, children }: ToggleProps) { const [on, setOnState] = useState(initial); const toggle = () => setOnState((prev) => !prev); const setOn = () => setOnState(true); const setOff = () => setOnState(false); return <>{children({ on, toggle, setOn, setOff })}</>;
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, ReactNode } from 'react'; interface ToggleState { on: boolean; toggle: () => void; setOn: () => void; setOff: () => void;
} interface ToggleProps { initial?: boolean; children: (state: ToggleState) => ReactNode;
} export function Toggle({ initial = false, children }: ToggleProps) { const [on, setOnState] = useState(initial); const toggle = () => setOnState((prev) => !prev); const setOn = () => setOnState(true); const setOff = () => setOnState(false); return <>{children({ on, toggle, setOn, setOff })}</>;
} COMMAND_BLOCK:
import { useState, ReactNode } from 'react'; interface ToggleState { on: boolean; toggle: () => void; setOn: () => void; setOff: () => void;
} interface ToggleProps { initial?: boolean; children: (state: ToggleState) => ReactNode;
} export function Toggle({ initial = false, children }: ToggleProps) { const [on, setOnState] = useState(initial); const toggle = () => setOnState((prev) => !prev); const setOn = () => setOnState(true); const setOff = () => setOnState(false); return <>{children({ on, toggle, setOn, setOff })}</>;
} COMMAND_BLOCK:
import { useState, ChangeEvent, FocusEvent, ReactNode } from 'react'; interface FieldState { value: string; error: string; touched: boolean;
} interface FieldHelpers { setValue: (value: string) => void; setError: (error: string) => void; setTouched: (touched: boolean) => void;
} interface FieldInputProps { value: string; onChange: (e: ChangeEvent<HTMLInputElement>) => void; onBlur: (e: FocusEvent<HTMLInputElement>) => void;
} interface FormFieldProps { name: string; initialValue?: string; validate?: (value: string) => string; children: ( state: FieldState, helpers: FieldHelpers, inputProps: FieldInputProps ) => ReactNode;
} export function FormField({ name, initialValue = '', validate, children,
}: FormFieldProps) { const [value, setValue] = useState(initialValue); const [error, setError] = useState(''); const [touched, setTouched] = useState(false); const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value; setValue(newValue); if (touched && validate) { const validationError = validate(newValue); setError(validationError); } }; const handleBlur = (e: FocusEvent<HTMLInputElement>) => { setTouched(true); if (validate) { const validationError = validate(value); setError(validationError); } }; const state: FieldState = { value, error, touched }; const helpers: FieldHelpers = { setValue, setError, setTouched, }; const inputProps: FieldInputProps = { value, onChange: handleChange, onBlur: handleBlur, }; return <>{children(state, helpers, inputProps)}</>;
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, ChangeEvent, FocusEvent, ReactNode } from 'react'; interface FieldState { value: string; error: string; touched: boolean;
} interface FieldHelpers { setValue: (value: string) => void; setError: (error: string) => void; setTouched: (touched: boolean) => void;
} interface FieldInputProps { value: string; onChange: (e: ChangeEvent<HTMLInputElement>) => void; onBlur: (e: FocusEvent<HTMLInputElement>) => void;
} interface FormFieldProps { name: string; initialValue?: string; validate?: (value: string) => string; children: ( state: FieldState, helpers: FieldHelpers, inputProps: FieldInputProps ) => ReactNode;
} export function FormField({ name, initialValue = '', validate, children,
}: FormFieldProps) { const [value, setValue] = useState(initialValue); const [error, setError] = useState(''); const [touched, setTouched] = useState(false); const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value; setValue(newValue); if (touched && validate) { const validationError = validate(newValue); setError(validationError); } }; const handleBlur = (e: FocusEvent<HTMLInputElement>) => { setTouched(true); if (validate) { const validationError = validate(value); setError(validationError); } }; const state: FieldState = { value, error, touched }; const helpers: FieldHelpers = { setValue, setError, setTouched, }; const inputProps: FieldInputProps = { value, onChange: handleChange, onBlur: handleBlur, }; return <>{children(state, helpers, inputProps)}</>;
} COMMAND_BLOCK:
import { useState, ChangeEvent, FocusEvent, ReactNode } from 'react'; interface FieldState { value: string; error: string; touched: boolean;
} interface FieldHelpers { setValue: (value: string) => void; setError: (error: string) => void; setTouched: (touched: boolean) => void;
} interface FieldInputProps { value: string; onChange: (e: ChangeEvent<HTMLInputElement>) => void; onBlur: (e: FocusEvent<HTMLInputElement>) => void;
} interface FormFieldProps { name: string; initialValue?: string; validate?: (value: string) => string; children: ( state: FieldState, helpers: FieldHelpers, inputProps: FieldInputProps ) => ReactNode;
} export function FormField({ name, initialValue = '', validate, children,
}: FormFieldProps) { const [value, setValue] = useState(initialValue); const [error, setError] = useState(''); const [touched, setTouched] = useState(false); const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value; setValue(newValue); if (touched && validate) { const validationError = validate(newValue); setError(validationError); } }; const handleBlur = (e: FocusEvent<HTMLInputElement>) => { setTouched(true); if (validate) { const validationError = validate(value); setError(validationError); } }; const state: FieldState = { value, error, touched }; const helpers: FieldHelpers = { setValue, setError, setTouched, }; const inputProps: FieldInputProps = { value, onChange: handleChange, onBlur: handleBlur, }; return <>{children(state, helpers, inputProps)}</>;
} COMMAND_BLOCK:
import { MouseTracker } from './MouseTracker';
import { DataFetcher } from './DataFetcher';
import { Toggle } from './Toggle';
import { FormField } from './FormField'; interface User { id: number; name: string; email: string;
} function Examples() { return ( <div> {/* Mouse Tracker Example */} <MouseTracker> {({ x, y }) => ( <div> <h2>Mouse Position</h2> <p> X: {x}, Y: {y} </p> <div style={{ position: 'absolute', left: x, top: y, width: 20, height: 20, borderRadius: '50%', backgroundColor: 'red', pointerEvents: 'none', }} /> </div> )} </MouseTracker> {/* Data Fetcher Example */} <DataFetcher<User[]> url="/api/users"> {({ data, loading, error }) => { if (loading) { return <div>Loading users...</div>; } if (error) { return <div>Error: {error.message}</div>; } if (!data) { return null; } return ( <ul> {data.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li> ))} </ul> ); }} </DataFetcher> {/* Toggle Example */} <Toggle initial={false}> {({ on, toggle }) => ( <div> <button onClick={toggle}> {on ? 'Hide' : 'Show'} Content </button> {on && ( <div> <h3>Toggleable Content</h3> <p>This content is visible when toggled on</p> </div> )} </div> )} </Toggle> {/* Form Field Example */} <form> <FormField name="email" validate={(value) => { if (!value) return 'Email is required'; if (!value.includes('@')) return 'Invalid email'; return ''; }} > {(state, helpers, inputProps) => ( <div> <label htmlFor="email">Email:</label> <input id="email" type="email" {...inputProps} style={{ borderColor: state.touched && state.error ? 'red' : 'gray', }} /> {state.touched && state.error && ( <span style={{ color: 'red' }}>{state.error}</span> )} </div> )} </FormField> </form> </div> );
} export default Examples; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { MouseTracker } from './MouseTracker';
import { DataFetcher } from './DataFetcher';
import { Toggle } from './Toggle';
import { FormField } from './FormField'; interface User { id: number; name: string; email: string;
} function Examples() { return ( <div> {/* Mouse Tracker Example */} <MouseTracker> {({ x, y }) => ( <div> <h2>Mouse Position</h2> <p> X: {x}, Y: {y} </p> <div style={{ position: 'absolute', left: x, top: y, width: 20, height: 20, borderRadius: '50%', backgroundColor: 'red', pointerEvents: 'none', }} /> </div> )} </MouseTracker> {/* Data Fetcher Example */} <DataFetcher<User[]> url="/api/users"> {({ data, loading, error }) => { if (loading) { return <div>Loading users...</div>; } if (error) { return <div>Error: {error.message}</div>; } if (!data) { return null; } return ( <ul> {data.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li> ))} </ul> ); }} </DataFetcher> {/* Toggle Example */} <Toggle initial={false}> {({ on, toggle }) => ( <div> <button onClick={toggle}> {on ? 'Hide' : 'Show'} Content </button> {on && ( <div> <h3>Toggleable Content</h3> <p>This content is visible when toggled on</p> </div> )} </div> )} </Toggle> {/* Form Field Example */} <form> <FormField name="email" validate={(value) => { if (!value) return 'Email is required'; if (!value.includes('@')) return 'Invalid email'; return ''; }} > {(state, helpers, inputProps) => ( <div> <label htmlFor="email">Email:</label> <input id="email" type="email" {...inputProps} style={{ borderColor: state.touched && state.error ? 'red' : 'gray', }} /> {state.touched && state.error && ( <span style={{ color: 'red' }}>{state.error}</span> )} </div> )} </FormField> </form> </div> );
} export default Examples; COMMAND_BLOCK:
import { MouseTracker } from './MouseTracker';
import { DataFetcher } from './DataFetcher';
import { Toggle } from './Toggle';
import { FormField } from './FormField'; interface User { id: number; name: string; email: string;
} function Examples() { return ( <div> {/* Mouse Tracker Example */} <MouseTracker> {({ x, y }) => ( <div> <h2>Mouse Position</h2> <p> X: {x}, Y: {y} </p> <div style={{ position: 'absolute', left: x, top: y, width: 20, height: 20, borderRadius: '50%', backgroundColor: 'red', pointerEvents: 'none', }} /> </div> )} </MouseTracker> {/* Data Fetcher Example */} <DataFetcher<User[]> url="/api/users"> {({ data, loading, error }) => { if (loading) { return <div>Loading users...</div>; } if (error) { return <div>Error: {error.message}</div>; } if (!data) { return null; } return ( <ul> {data.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li> ))} </ul> ); }} </DataFetcher> {/* Toggle Example */} <Toggle initial={false}> {({ on, toggle }) => ( <div> <button onClick={toggle}> {on ? 'Hide' : 'Show'} Content </button> {on && ( <div> <h3>Toggleable Content</h3> <p>This content is visible when toggled on</p> </div> )} </div> )} </Toggle> {/* Form Field Example */} <form> <FormField name="email" validate={(value) => { if (!value) return 'Email is required'; if (!value.includes('@')) return 'Invalid email'; return ''; }} > {(state, helpers, inputProps) => ( <div> <label htmlFor="email">Email:</label> <input id="email" type="email" {...inputProps} style={{ borderColor: state.touched && state.error ? 'red' : 'gray', }} /> {state.touched && state.error && ( <span style={{ color: 'red' }}>{state.error}</span> )} </div> )} </FormField> </form> </div> );
} export default Examples; COMMAND_BLOCK:
import { Formik, Form, Field, ErrorMessage } from 'formik'; interface FormValues { email: string; password: string;
} function LoginForm() { const initialValues: FormValues = { email: '', password: '', }; const validate = (values: FormValues) => { const errors: Partial<FormValues> = {}; if (!values.email) { errors.email = 'Required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) { errors.email = 'Invalid email address'; } if (!values.password) { errors.password = 'Required'; } else if (values.password.length < 6) { errors.password = 'Password must be at least 6 characters'; } return errors; }; const onSubmit = (values: FormValues) => { console.log('Form submitted:', values); }; return ( <Formik initialValues={initialValues} validate={validate} onSubmit={onSubmit} > {({ isSubmitting, errors, touched }) => ( <Form> <div> <label htmlFor="email">Email</label> <Field type="email" name="email" /> {errors.email && touched.email && ( <div>{errors.email}</div> )} </div> <div> <label htmlFor="password">Password</label> <Field type="password" name="password" /> {errors.password && touched.password && ( <div>{errors.password}</div> )} </div> <button type="submit" disabled={isSubmitting}> Submit </button> </Form> )} </Formik> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Formik, Form, Field, ErrorMessage } from 'formik'; interface FormValues { email: string; password: string;
} function LoginForm() { const initialValues: FormValues = { email: '', password: '', }; const validate = (values: FormValues) => { const errors: Partial<FormValues> = {}; if (!values.email) { errors.email = 'Required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) { errors.email = 'Invalid email address'; } if (!values.password) { errors.password = 'Required'; } else if (values.password.length < 6) { errors.password = 'Password must be at least 6 characters'; } return errors; }; const onSubmit = (values: FormValues) => { console.log('Form submitted:', values); }; return ( <Formik initialValues={initialValues} validate={validate} onSubmit={onSubmit} > {({ isSubmitting, errors, touched }) => ( <Form> <div> <label htmlFor="email">Email</label> <Field type="email" name="email" /> {errors.email && touched.email && ( <div>{errors.email}</div> )} </div> <div> <label htmlFor="password">Password</label> <Field type="password" name="password" /> {errors.password && touched.password && ( <div>{errors.password}</div> )} </div> <button type="submit" disabled={isSubmitting}> Submit </button> </Form> )} </Formik> );
} COMMAND_BLOCK:
import { Formik, Form, Field, ErrorMessage } from 'formik'; interface FormValues { email: string; password: string;
} function LoginForm() { const initialValues: FormValues = { email: '', password: '', }; const validate = (values: FormValues) => { const errors: Partial<FormValues> = {}; if (!values.email) { errors.email = 'Required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) { errors.email = 'Invalid email address'; } if (!values.password) { errors.password = 'Required'; } else if (values.password.length < 6) { errors.password = 'Password must be at least 6 characters'; } return errors; }; const onSubmit = (values: FormValues) => { console.log('Form submitted:', values); }; return ( <Formik initialValues={initialValues} validate={validate} onSubmit={onSubmit} > {({ isSubmitting, errors, touched }) => ( <Form> <div> <label htmlFor="email">Email</label> <Field type="email" name="email" /> {errors.email && touched.email && ( <div>{errors.email}</div> )} </div> <div> <label htmlFor="password">Password</label> <Field type="password" name="password" /> {errors.password && touched.password && ( <div>{errors.password}</div> )} </div> <button type="submit" disabled={isSubmitting}> Submit </button> </Form> )} </Formik> );
} COMMAND_BLOCK:
import { useFormik } from 'formik'; interface FormValues { email: string; password: string;
} function LoginForm() { const formik = useFormik<FormValues>({ initialValues: { email: '', password: '', }, validate: (values) => { const errors: Partial<FormValues> = {}; if (!values.email) { errors.email = 'Required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) { errors.email = 'Invalid email address'; } if (!values.password) { errors.password = 'Required'; } return errors; }, onSubmit: (values) => { console.log('Form submitted:', values); }, }); return ( <form onSubmit={formik.handleSubmit}> <div> <label htmlFor="email">Email</label> <input id="email" type="email" {...formik.getFieldProps('email')} /> {formik.touched.email && formik.errors.email && ( <div>{formik.errors.email}</div> )} </div> <div> <label htmlFor="password">Password</label> <input id="password" type="password" {...formik.getFieldProps('password')} /> {formik.touched.password && formik.errors.password && ( <div>{formik.errors.password}</div> )} </div> <button type="submit" disabled={formik.isSubmitting}> Submit </button> </form> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useFormik } from 'formik'; interface FormValues { email: string; password: string;
} function LoginForm() { const formik = useFormik<FormValues>({ initialValues: { email: '', password: '', }, validate: (values) => { const errors: Partial<FormValues> = {}; if (!values.email) { errors.email = 'Required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) { errors.email = 'Invalid email address'; } if (!values.password) { errors.password = 'Required'; } return errors; }, onSubmit: (values) => { console.log('Form submitted:', values); }, }); return ( <form onSubmit={formik.handleSubmit}> <div> <label htmlFor="email">Email</label> <input id="email" type="email" {...formik.getFieldProps('email')} /> {formik.touched.email && formik.errors.email && ( <div>{formik.errors.email}</div> )} </div> <div> <label htmlFor="password">Password</label> <input id="password" type="password" {...formik.getFieldProps('password')} /> {formik.touched.password && formik.errors.password && ( <div>{formik.errors.password}</div> )} </div> <button type="submit" disabled={formik.isSubmitting}> Submit </button> </form> );
} COMMAND_BLOCK:
import { useFormik } from 'formik'; interface FormValues { email: string; password: string;
} function LoginForm() { const formik = useFormik<FormValues>({ initialValues: { email: '', password: '', }, validate: (values) => { const errors: Partial<FormValues> = {}; if (!values.email) { errors.email = 'Required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) { errors.email = 'Invalid email address'; } if (!values.password) { errors.password = 'Required'; } return errors; }, onSubmit: (values) => { console.log('Form submitted:', values); }, }); return ( <form onSubmit={formik.handleSubmit}> <div> <label htmlFor="email">Email</label> <input id="email" type="email" {...formik.getFieldProps('email')} /> {formik.touched.email && formik.errors.email && ( <div>{formik.errors.email}</div> )} </div> <div> <label htmlFor="password">Password</label> <input id="password" type="password" {...formik.getFieldProps('password')} /> {formik.touched.password && formik.errors.password && ( <div>{formik.errors.password}</div> )} </div> <button type="submit" disabled={formik.isSubmitting}> Submit </button> </form> );
} COMMAND_BLOCK:
import Downshift from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; return ( <Downshift onChange={(selection) => alert(`You selected ${selection?.value}`)} itemToString={(item) => (item ? item.value : '')} > {({ getInputProps, getItemProps, getLabelProps, getMenuProps, isOpen, inputValue, highlightedIndex, selectedItem, getRootProps, }) => ( <div> <label {...getLabelProps()}>Select a fruit</label> <div {...getRootProps(undefined, { suppressRefError: true })}> <input {...getInputProps()} /> </div> <ul {...getMenuProps()}> {isOpen ? items .filter((item) => !inputValue || item.value.includes(inputValue)) .map((item, index) => ( <li {...getItemProps({ key: item.id, index, item, style: { backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }, })} > {item.value} </li> )) : null} </ul> </div> )} </Downshift> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import Downshift from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; return ( <Downshift onChange={(selection) => alert(`You selected ${selection?.value}`)} itemToString={(item) => (item ? item.value : '')} > {({ getInputProps, getItemProps, getLabelProps, getMenuProps, isOpen, inputValue, highlightedIndex, selectedItem, getRootProps, }) => ( <div> <label {...getLabelProps()}>Select a fruit</label> <div {...getRootProps(undefined, { suppressRefError: true })}> <input {...getInputProps()} /> </div> <ul {...getMenuProps()}> {isOpen ? items .filter((item) => !inputValue || item.value.includes(inputValue)) .map((item, index) => ( <li {...getItemProps({ key: item.id, index, item, style: { backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }, })} > {item.value} </li> )) : null} </ul> </div> )} </Downshift> );
} COMMAND_BLOCK:
import Downshift from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; return ( <Downshift onChange={(selection) => alert(`You selected ${selection?.value}`)} itemToString={(item) => (item ? item.value : '')} > {({ getInputProps, getItemProps, getLabelProps, getMenuProps, isOpen, inputValue, highlightedIndex, selectedItem, getRootProps, }) => ( <div> <label {...getLabelProps()}>Select a fruit</label> <div {...getRootProps(undefined, { suppressRefError: true })}> <input {...getInputProps()} /> </div> <ul {...getMenuProps()}> {isOpen ? items .filter((item) => !inputValue || item.value.includes(inputValue)) .map((item, index) => ( <li {...getItemProps({ key: item.id, index, item, style: { backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }, })} > {item.value} </li> )) : null} </ul> </div> )} </Downshift> );
} COMMAND_BLOCK:
import { useSelect } from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps, } = useSelect({ items, onSelectedItemChange: ({ selectedItem }) => { alert(`You selected ${selectedItem?.value}`); }, }); return ( <div> <label {...getLabelProps()}>Select a fruit</label> <button type="button" {...getToggleButtonProps()}> {selectedItem ? selectedItem.value : 'Select...'} </button> <ul {...getMenuProps()}> {isOpen && items.map((item, index) => ( <li key={item.id} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }} > {item.value} </li> ))} </ul> </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useSelect } from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps, } = useSelect({ items, onSelectedItemChange: ({ selectedItem }) => { alert(`You selected ${selectedItem?.value}`); }, }); return ( <div> <label {...getLabelProps()}>Select a fruit</label> <button type="button" {...getToggleButtonProps()}> {selectedItem ? selectedItem.value : 'Select...'} </button> <ul {...getMenuProps()}> {isOpen && items.map((item, index) => ( <li key={item.id} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }} > {item.value} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useSelect } from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps, } = useSelect({ items, onSelectedItemChange: ({ selectedItem }) => { alert(`You selected ${selectedItem?.value}`); }, }); return ( <div> <label {...getLabelProps()}>Select a fruit</label> <button type="button" {...getToggleButtonProps()}> {selectedItem ? selectedItem.value : 'Select...'} </button> <ul {...getMenuProps()}> {isOpen && items.map((item, index) => ( <li key={item.id} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }} > {item.value} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { Motion, spring } from 'react-motion'; function AnimatedComponent() { return ( <Motion defaultStyle={{ x: 0 }} style={{ x: spring(100) }}> {(style) => ( <div style={{ transform: `translateX(${style.x}px)`, width: 50, height: 50, backgroundColor: 'blue', }} /> )} </Motion> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Motion, spring } from 'react-motion'; function AnimatedComponent() { return ( <Motion defaultStyle={{ x: 0 }} style={{ x: spring(100) }}> {(style) => ( <div style={{ transform: `translateX(${style.x}px)`, width: 50, height: 50, backgroundColor: 'blue', }} /> )} </Motion> );
} COMMAND_BLOCK:
import { Motion, spring } from 'react-motion'; function AnimatedComponent() { return ( <Motion defaultStyle={{ x: 0 }} style={{ x: spring(100) }}> {(style) => ( <div style={{ transform: `translateX(${style.x}px)`, width: 50, height: 50, backgroundColor: 'blue', }} /> )} </Motion> );
} COMMAND_BLOCK:
import Measure from 'react-measure'; function MeasuredComponent() { return ( <Measure bounds> {({ measureRef, contentRect }) => ( <div ref={measureRef}> <p>Width: {contentRect.bounds?.width}</p> <p>Height: {contentRect.bounds?.height}</p> </div> )} </Measure> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import Measure from 'react-measure'; function MeasuredComponent() { return ( <Measure bounds> {({ measureRef, contentRect }) => ( <div ref={measureRef}> <p>Width: {contentRect.bounds?.width}</p> <p>Height: {contentRect.bounds?.height}</p> </div> )} </Measure> );
} COMMAND_BLOCK:
import Measure from 'react-measure'; function MeasuredComponent() { return ( <Measure bounds> {({ measureRef, contentRect }) => ( <div ref={measureRef}> <p>Width: {contentRect.bounds?.width}</p> <p>Height: {contentRect.bounds?.height}</p> </div> )} </Measure> );
} COMMAND_BLOCK:
<DataFetcher url="/api/users"> {(users) => ( <DataFetcher url="/api/posts"> {(posts) => ( <DataFetcher url="/api/comments"> {(comments) => ( // Código aquí con tres niveles de indentación <div>...</div> )} </DataFetcher> )} </DataFetcher> )}
</DataFetcher> Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
<DataFetcher url="/api/users"> {(users) => ( <DataFetcher url="/api/posts"> {(posts) => ( <DataFetcher url="/api/comments"> {(comments) => ( // Código aquí con tres niveles de indentación <div>...</div> )} </DataFetcher> )} </DataFetcher> )}
</DataFetcher> COMMAND_BLOCK:
<DataFetcher url="/api/users"> {(users) => ( <DataFetcher url="/api/posts"> {(posts) => ( <DataFetcher url="/api/comments"> {(comments) => ( // Código aquí con tres niveles de indentación <div>...</div> )} </DataFetcher> )} </DataFetcher> )}
</DataFetcher> CODE_BLOCK:
<input value={state} onChange={handleChange} /> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
<input value={state} onChange={handleChange} /> CODE_BLOCK:
<input value={state} onChange={handleChange} /> CODE_BLOCK:
<input defaultValue="initial" ref={inputRef} /> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
<input defaultValue="initial" ref={inputRef} /> CODE_BLOCK:
<input defaultValue="initial" ref={inputRef} /> COMMAND_BLOCK:
import { useState, ChangeEvent, FormEvent } from 'react'; interface FormData { email: string; password: string; age: string; country: string; acceptTerms: boolean;
} interface FormErrors { email?: string; password?: string; age?: string;
} export function ControlledForm() { const [formData, setFormData] = useState<FormData>({ email: '', password: '', age: '', country: 'mx', acceptTerms: false, }); const [errors, setErrors] = useState<FormErrors>({}); const [touched, setTouched] = useState<Record<string, boolean>>({}); const validateField = (name: keyof FormData, value: string | boolean): string => { switch (name) { case 'email': if (!value) return 'Email is required'; if (typeof value === 'string' && !value.includes('@')) { return 'Invalid email format'; } return ''; case 'password': if (!value) return 'Password is required'; if (typeof value === 'string' && value.length < 8) { return 'Password must be at least 8 characters'; } return ''; case 'age': if (value) { const ageNum = parseInt(value as string); if (isNaN(ageNum) || ageNum < 18) { return 'Must be 18 or older'; } } return ''; default: return ''; } }; const handleChange = ( e: ChangeEvent<HTMLInputElement | HTMLSelectElement> ) => { const { name, value, type } = e.target; const checked = (e.target as HTMLInputElement).checked; const fieldValue = type === 'checkbox' ? checked : value; setFormData(prev => ({ ...prev, [name]: fieldValue, })); if (touched[name]) { const error = validateField(name as keyof FormData, fieldValue); setErrors(prev => ({ ...prev, [name]: error, })); } }; const handleBlur = (e: ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; setTouched(prev => ({ ...prev, [name]: true, })); const error = validateField(name as keyof FormData, value); setErrors(prev => ({ ...prev, [name]: error, })); }; const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const allTouched = Object.keys(formData).reduce( (acc, key) => ({ ...acc, [key]: true }), {} ); setTouched(allTouched); const newErrors: FormErrors = {}; (Object.keys(formData) as Array<keyof FormData>).forEach(key => { const error = validateField(key, formData[key]); if (error) newErrors[key as keyof FormErrors] = error; }); setErrors(newErrors); if (Object.keys(newErrors).length === 0) { console.log('Form submitted:', formData); alert('Form submitted successfully!'); } }; return ( <form onSubmit={handleSubmit} noValidate> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" value={formData.email} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.email && !!errors.email} aria-describedby={errors.email ? 'email-error' : undefined} /> {touched.email && errors.email && ( <span id="email-error" role="alert" style={{ color: 'red' }}> {errors.email} </span> )} </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password" value={formData.password} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.password && !!errors.password} aria-describedby={errors.password ? 'password-error' : undefined} /> {touched.password && errors.password && ( <span id="password-error" role="alert" style={{ color: 'red' }}> {errors.password} </span> )} </div> <div> <label htmlFor="age">Age:</label> <input type="number" id="age" name="age" value={formData.age} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.age && !!errors.age} aria-describedby={errors.age ? 'age-error' : undefined} /> {touched.age && errors.age && ( <span id="age-error" role="alert" style={{ color: 'red' }}> {errors.age} </span> )} </div> <div> <label htmlFor="country">Country:</label> <select id="country" name="country" value={formData.country} onChange={handleChange} > <option value="mx">Mexico</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="es">Spain</option> </select> </div> <div> <label> <input type="checkbox" name="acceptTerms" checked={formData.acceptTerms} onChange={handleChange} /> I accept the terms and conditions </label> </div> <button type="submit">Submit</button> </form> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, ChangeEvent, FormEvent } from 'react'; interface FormData { email: string; password: string; age: string; country: string; acceptTerms: boolean;
} interface FormErrors { email?: string; password?: string; age?: string;
} export function ControlledForm() { const [formData, setFormData] = useState<FormData>({ email: '', password: '', age: '', country: 'mx', acceptTerms: false, }); const [errors, setErrors] = useState<FormErrors>({}); const [touched, setTouched] = useState<Record<string, boolean>>({}); const validateField = (name: keyof FormData, value: string | boolean): string => { switch (name) { case 'email': if (!value) return 'Email is required'; if (typeof value === 'string' && !value.includes('@')) { return 'Invalid email format'; } return ''; case 'password': if (!value) return 'Password is required'; if (typeof value === 'string' && value.length < 8) { return 'Password must be at least 8 characters'; } return ''; case 'age': if (value) { const ageNum = parseInt(value as string); if (isNaN(ageNum) || ageNum < 18) { return 'Must be 18 or older'; } } return ''; default: return ''; } }; const handleChange = ( e: ChangeEvent<HTMLInputElement | HTMLSelectElement> ) => { const { name, value, type } = e.target; const checked = (e.target as HTMLInputElement).checked; const fieldValue = type === 'checkbox' ? checked : value; setFormData(prev => ({ ...prev, [name]: fieldValue, })); if (touched[name]) { const error = validateField(name as keyof FormData, fieldValue); setErrors(prev => ({ ...prev, [name]: error, })); } }; const handleBlur = (e: ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; setTouched(prev => ({ ...prev, [name]: true, })); const error = validateField(name as keyof FormData, value); setErrors(prev => ({ ...prev, [name]: error, })); }; const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const allTouched = Object.keys(formData).reduce( (acc, key) => ({ ...acc, [key]: true }), {} ); setTouched(allTouched); const newErrors: FormErrors = {}; (Object.keys(formData) as Array<keyof FormData>).forEach(key => { const error = validateField(key, formData[key]); if (error) newErrors[key as keyof FormErrors] = error; }); setErrors(newErrors); if (Object.keys(newErrors).length === 0) { console.log('Form submitted:', formData); alert('Form submitted successfully!'); } }; return ( <form onSubmit={handleSubmit} noValidate> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" value={formData.email} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.email && !!errors.email} aria-describedby={errors.email ? 'email-error' : undefined} /> {touched.email && errors.email && ( <span id="email-error" role="alert" style={{ color: 'red' }}> {errors.email} </span> )} </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password" value={formData.password} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.password && !!errors.password} aria-describedby={errors.password ? 'password-error' : undefined} /> {touched.password && errors.password && ( <span id="password-error" role="alert" style={{ color: 'red' }}> {errors.password} </span> )} </div> <div> <label htmlFor="age">Age:</label> <input type="number" id="age" name="age" value={formData.age} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.age && !!errors.age} aria-describedby={errors.age ? 'age-error' : undefined} /> {touched.age && errors.age && ( <span id="age-error" role="alert" style={{ color: 'red' }}> {errors.age} </span> )} </div> <div> <label htmlFor="country">Country:</label> <select id="country" name="country" value={formData.country} onChange={handleChange} > <option value="mx">Mexico</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="es">Spain</option> </select> </div> <div> <label> <input type="checkbox" name="acceptTerms" checked={formData.acceptTerms} onChange={handleChange} /> I accept the terms and conditions </label> </div> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useState, ChangeEvent, FormEvent } from 'react'; interface FormData { email: string; password: string; age: string; country: string; acceptTerms: boolean;
} interface FormErrors { email?: string; password?: string; age?: string;
} export function ControlledForm() { const [formData, setFormData] = useState<FormData>({ email: '', password: '', age: '', country: 'mx', acceptTerms: false, }); const [errors, setErrors] = useState<FormErrors>({}); const [touched, setTouched] = useState<Record<string, boolean>>({}); const validateField = (name: keyof FormData, value: string | boolean): string => { switch (name) { case 'email': if (!value) return 'Email is required'; if (typeof value === 'string' && !value.includes('@')) { return 'Invalid email format'; } return ''; case 'password': if (!value) return 'Password is required'; if (typeof value === 'string' && value.length < 8) { return 'Password must be at least 8 characters'; } return ''; case 'age': if (value) { const ageNum = parseInt(value as string); if (isNaN(ageNum) || ageNum < 18) { return 'Must be 18 or older'; } } return ''; default: return ''; } }; const handleChange = ( e: ChangeEvent<HTMLInputElement | HTMLSelectElement> ) => { const { name, value, type } = e.target; const checked = (e.target as HTMLInputElement).checked; const fieldValue = type === 'checkbox' ? checked : value; setFormData(prev => ({ ...prev, [name]: fieldValue, })); if (touched[name]) { const error = validateField(name as keyof FormData, fieldValue); setErrors(prev => ({ ...prev, [name]: error, })); } }; const handleBlur = (e: ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; setTouched(prev => ({ ...prev, [name]: true, })); const error = validateField(name as keyof FormData, value); setErrors(prev => ({ ...prev, [name]: error, })); }; const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const allTouched = Object.keys(formData).reduce( (acc, key) => ({ ...acc, [key]: true }), {} ); setTouched(allTouched); const newErrors: FormErrors = {}; (Object.keys(formData) as Array<keyof FormData>).forEach(key => { const error = validateField(key, formData[key]); if (error) newErrors[key as keyof FormErrors] = error; }); setErrors(newErrors); if (Object.keys(newErrors).length === 0) { console.log('Form submitted:', formData); alert('Form submitted successfully!'); } }; return ( <form onSubmit={handleSubmit} noValidate> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" value={formData.email} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.email && !!errors.email} aria-describedby={errors.email ? 'email-error' : undefined} /> {touched.email && errors.email && ( <span id="email-error" role="alert" style={{ color: 'red' }}> {errors.email} </span> )} </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password" value={formData.password} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.password && !!errors.password} aria-describedby={errors.password ? 'password-error' : undefined} /> {touched.password && errors.password && ( <span id="password-error" role="alert" style={{ color: 'red' }}> {errors.password} </span> )} </div> <div> <label htmlFor="age">Age:</label> <input type="number" id="age" name="age" value={formData.age} onChange={handleChange} onBlur={handleBlur} aria-invalid={touched.age && !!errors.age} aria-describedby={errors.age ? 'age-error' : undefined} /> {touched.age && errors.age && ( <span id="age-error" role="alert" style={{ color: 'red' }}> {errors.age} </span> )} </div> <div> <label htmlFor="country">Country:</label> <select id="country" name="country" value={formData.country} onChange={handleChange} > <option value="mx">Mexico</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="es">Spain</option> </select> </div> <div> <label> <input type="checkbox" name="acceptTerms" checked={formData.acceptTerms} onChange={handleChange} /> I accept the terms and conditions </label> </div> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useRef, FormEvent } from 'react'; export function UncontrolledForm() { const emailRef = useRef<HTMLInputElement>(null); const passwordRef = useRef<HTMLInputElement>(null); const ageRef = useRef<HTMLInputElement>(null); const countryRef = useRef<HTMLSelectElement>(null); const termsRef = useRef<HTMLInputElement>(null); const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = { email: emailRef.current?.value || '', password: passwordRef.current?.value || '', age: ageRef.current?.value || '', country: countryRef.current?.value || '', acceptTerms: termsRef.current?.checked || false, }; if (!formData.email.includes('@')) { alert('Please enter a valid email'); emailRef.current?.focus(); return; } if (formData.password.length < 8) { alert('Password must be at least 8 characters'); passwordRef.current?.focus(); return; } console.log('Form submitted:', formData); alert('Form submitted successfully!'); }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" ref={emailRef} defaultValue="" /> </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password" ref={passwordRef} defaultValue="" /> </div> <div> <label htmlFor="age">Age:</label> <input type="number" id="age" name="age" ref={ageRef} defaultValue="" /> </div> <div> <label htmlFor="country">Country:</label> <select id="country" name="country" ref={countryRef} defaultValue="mx" > <option value="mx">Mexico</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="es">Spain</option> </select> </div> <div> <label> <input type="checkbox" name="acceptTerms" ref={termsRef} defaultChecked={false} /> I accept the terms and conditions </label> </div> <button type="submit">Submit</button> </form> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useRef, FormEvent } from 'react'; export function UncontrolledForm() { const emailRef = useRef<HTMLInputElement>(null); const passwordRef = useRef<HTMLInputElement>(null); const ageRef = useRef<HTMLInputElement>(null); const countryRef = useRef<HTMLSelectElement>(null); const termsRef = useRef<HTMLInputElement>(null); const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = { email: emailRef.current?.value || '', password: passwordRef.current?.value || '', age: ageRef.current?.value || '', country: countryRef.current?.value || '', acceptTerms: termsRef.current?.checked || false, }; if (!formData.email.includes('@')) { alert('Please enter a valid email'); emailRef.current?.focus(); return; } if (formData.password.length < 8) { alert('Password must be at least 8 characters'); passwordRef.current?.focus(); return; } console.log('Form submitted:', formData); alert('Form submitted successfully!'); }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" ref={emailRef} defaultValue="" /> </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password" ref={passwordRef} defaultValue="" /> </div> <div> <label htmlFor="age">Age:</label> <input type="number" id="age" name="age" ref={ageRef} defaultValue="" /> </div> <div> <label htmlFor="country">Country:</label> <select id="country" name="country" ref={countryRef} defaultValue="mx" > <option value="mx">Mexico</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="es">Spain</option> </select> </div> <div> <label> <input type="checkbox" name="acceptTerms" ref={termsRef} defaultChecked={false} /> I accept the terms and conditions </label> </div> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useRef, FormEvent } from 'react'; export function UncontrolledForm() { const emailRef = useRef<HTMLInputElement>(null); const passwordRef = useRef<HTMLInputElement>(null); const ageRef = useRef<HTMLInputElement>(null); const countryRef = useRef<HTMLSelectElement>(null); const termsRef = useRef<HTMLInputElement>(null); const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = { email: emailRef.current?.value || '', password: passwordRef.current?.value || '', age: ageRef.current?.value || '', country: countryRef.current?.value || '', acceptTerms: termsRef.current?.checked || false, }; if (!formData.email.includes('@')) { alert('Please enter a valid email'); emailRef.current?.focus(); return; } if (formData.password.length < 8) { alert('Password must be at least 8 characters'); passwordRef.current?.focus(); return; } console.log('Form submitted:', formData); alert('Form submitted successfully!'); }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="email">Email:</label> <input type="email" id="email" name="email" ref={emailRef} defaultValue="" /> </div> <div> <label htmlFor="password">Password:</label> <input type="password" id="password" name="password" ref={passwordRef} defaultValue="" /> </div> <div> <label htmlFor="age">Age:</label> <input type="number" id="age" name="age" ref={ageRef} defaultValue="" /> </div> <div> <label htmlFor="country">Country:</label> <select id="country" name="country" ref={countryRef} defaultValue="mx" > <option value="mx">Mexico</option> <option value="us">United States</option> <option value="ca">Canada</option> <option value="es">Spain</option> </select> </div> <div> <label> <input type="checkbox" name="acceptTerms" ref={termsRef} defaultChecked={false} /> I accept the terms and conditions </label> </div> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useState, useEffect, ChangeEvent } from 'react'; interface HybridInputProps { value?: string; defaultValue?: string; onChange?: (e: ChangeEvent<HTMLInputElement>) => void; name?: string; placeholder?: string;
} export function HybridInput({ value, defaultValue, onChange, name, placeholder,
}: HybridInputProps) { const isControlled = value !== undefined; const [internalValue, setInternalValue] = useState(defaultValue || ''); useEffect(() => { if (!isControlled && defaultValue !== undefined) { setInternalValue(defaultValue); } }, [defaultValue, isControlled]); const currentValue = isControlled ? value : internalValue; const handleChange = (e: ChangeEvent<HTMLInputElement>) => { if (!isControlled) { setInternalValue(e.target.value); } onChange?.(e); }; return ( <input type="text" name={name} value={currentValue} onChange={handleChange} placeholder={placeholder} /> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, useEffect, ChangeEvent } from 'react'; interface HybridInputProps { value?: string; defaultValue?: string; onChange?: (e: ChangeEvent<HTMLInputElement>) => void; name?: string; placeholder?: string;
} export function HybridInput({ value, defaultValue, onChange, name, placeholder,
}: HybridInputProps) { const isControlled = value !== undefined; const [internalValue, setInternalValue] = useState(defaultValue || ''); useEffect(() => { if (!isControlled && defaultValue !== undefined) { setInternalValue(defaultValue); } }, [defaultValue, isControlled]); const currentValue = isControlled ? value : internalValue; const handleChange = (e: ChangeEvent<HTMLInputElement>) => { if (!isControlled) { setInternalValue(e.target.value); } onChange?.(e); }; return ( <input type="text" name={name} value={currentValue} onChange={handleChange} placeholder={placeholder} /> );
} COMMAND_BLOCK:
import { useState, useEffect, ChangeEvent } from 'react'; interface HybridInputProps { value?: string; defaultValue?: string; onChange?: (e: ChangeEvent<HTMLInputElement>) => void; name?: string; placeholder?: string;
} export function HybridInput({ value, defaultValue, onChange, name, placeholder,
}: HybridInputProps) { const isControlled = value !== undefined; const [internalValue, setInternalValue] = useState(defaultValue || ''); useEffect(() => { if (!isControlled && defaultValue !== undefined) { setInternalValue(defaultValue); } }, [defaultValue, isControlled]); const currentValue = isControlled ? value : internalValue; const handleChange = (e: ChangeEvent<HTMLInputElement>) => { if (!isControlled) { setInternalValue(e.target.value); } onChange?.(e); }; return ( <input type="text" name={name} value={currentValue} onChange={handleChange} placeholder={placeholder} /> );
} COMMAND_BLOCK:
import { useRef, ChangeEvent, useState } from 'react'; export function FileUpload() { const fileInputRef = useRef<HTMLInputElement>(null); const [selectedFile, setSelectedFile] = useState<File | null>(null); const [preview, setPreview] = useState<string>(''); const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { setSelectedFile(file); if (file.type.startsWith('image/')) { const reader = new FileReader(); reader.onloadend = () => { setPreview(reader.result as string); }; reader.readAsDataURL(file); } } }; const handleUpload = () => { if (!selectedFile) { alert('Please select a file first'); return; } console.log('Uploading file:', selectedFile.name); alert(`File "${selectedFile.name}" ready to upload`); }; const handleClear = () => { setSelectedFile(null); setPreview(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; return ( <div> <input type="file" ref={fileInputRef} onChange={handleFileChange} accept="image/*" /> {preview && ( <div> <h3>Preview:</h3> <img src={preview} alt="Preview" style={{ maxWidth: '300px', maxHeight: '300px' }} /> </div> )} {selectedFile && ( <div> <p>Selected: {selectedFile.name}</p> <p>Size: {(selectedFile.size / 1024).toFixed(2)} KB</p> <button type="button" onClick={handleUpload}> Upload </button> <button type="button" onClick={handleClear}> Clear </button> </div> )} </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useRef, ChangeEvent, useState } from 'react'; export function FileUpload() { const fileInputRef = useRef<HTMLInputElement>(null); const [selectedFile, setSelectedFile] = useState<File | null>(null); const [preview, setPreview] = useState<string>(''); const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { setSelectedFile(file); if (file.type.startsWith('image/')) { const reader = new FileReader(); reader.onloadend = () => { setPreview(reader.result as string); }; reader.readAsDataURL(file); } } }; const handleUpload = () => { if (!selectedFile) { alert('Please select a file first'); return; } console.log('Uploading file:', selectedFile.name); alert(`File "${selectedFile.name}" ready to upload`); }; const handleClear = () => { setSelectedFile(null); setPreview(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; return ( <div> <input type="file" ref={fileInputRef} onChange={handleFileChange} accept="image/*" /> {preview && ( <div> <h3>Preview:</h3> <img src={preview} alt="Preview" style={{ maxWidth: '300px', maxHeight: '300px' }} /> </div> )} {selectedFile && ( <div> <p>Selected: {selectedFile.name}</p> <p>Size: {(selectedFile.size / 1024).toFixed(2)} KB</p> <button type="button" onClick={handleUpload}> Upload </button> <button type="button" onClick={handleClear}> Clear </button> </div> )} </div> );
} COMMAND_BLOCK:
import { useRef, ChangeEvent, useState } from 'react'; export function FileUpload() { const fileInputRef = useRef<HTMLInputElement>(null); const [selectedFile, setSelectedFile] = useState<File | null>(null); const [preview, setPreview] = useState<string>(''); const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (file) { setSelectedFile(file); if (file.type.startsWith('image/')) { const reader = new FileReader(); reader.onloadend = () => { setPreview(reader.result as string); }; reader.readAsDataURL(file); } } }; const handleUpload = () => { if (!selectedFile) { alert('Please select a file first'); return; } console.log('Uploading file:', selectedFile.name); alert(`File "${selectedFile.name}" ready to upload`); }; const handleClear = () => { setSelectedFile(null); setPreview(''); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; return ( <div> <input type="file" ref={fileInputRef} onChange={handleFileChange} accept="image/*" /> {preview && ( <div> <h3>Preview:</h3> <img src={preview} alt="Preview" style={{ maxWidth: '300px', maxHeight: '300px' }} /> </div> )} {selectedFile && ( <div> <p>Selected: {selectedFile.name}</p> <p>Size: {(selectedFile.size / 1024).toFixed(2)} KB</p> <button type="button" onClick={handleUpload}> Upload </button> <button type="button" onClick={handleClear}> Clear </button> </div> )} </div> );
} COMMAND_BLOCK:
import { useForm, SubmitHandler } from 'react-hook-form'; interface FormInputs { email: string; password: string; age: number;
} function RHFForm() { const { register, handleSubmit, formState: { errors }, } = useForm<FormInputs>(); const onSubmit: SubmitHandler<FormInputs> = (data) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <input {...register('email', { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Invalid email address', }, })} placeholder="Email" /> {errors.email && <span>{errors.email.message}</span>} </div> <div> <input type="password" {...register('password', { required: 'Password is required', minLength: { value: 8, message: 'Password must be at least 8 characters', }, })} placeholder="Password" /> {errors.password && <span>{errors.password.message}</span>} </div> <div> <input type="number" {...register('age', { valueAsNumber: true, min: { value: 18, message: 'Must be 18 or older', }, })} placeholder="Age" /> {errors.age && <span>{errors.age.message}</span>} </div> <button type="submit">Submit</button> </form> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useForm, SubmitHandler } from 'react-hook-form'; interface FormInputs { email: string; password: string; age: number;
} function RHFForm() { const { register, handleSubmit, formState: { errors }, } = useForm<FormInputs>(); const onSubmit: SubmitHandler<FormInputs> = (data) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <input {...register('email', { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Invalid email address', }, })} placeholder="Email" /> {errors.email && <span>{errors.email.message}</span>} </div> <div> <input type="password" {...register('password', { required: 'Password is required', minLength: { value: 8, message: 'Password must be at least 8 characters', }, })} placeholder="Password" /> {errors.password && <span>{errors.password.message}</span>} </div> <div> <input type="number" {...register('age', { valueAsNumber: true, min: { value: 18, message: 'Must be 18 or older', }, })} placeholder="Age" /> {errors.age && <span>{errors.age.message}</span>} </div> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useForm, SubmitHandler } from 'react-hook-form'; interface FormInputs { email: string; password: string; age: number;
} function RHFForm() { const { register, handleSubmit, formState: { errors }, } = useForm<FormInputs>(); const onSubmit: SubmitHandler<FormInputs> = (data) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <input {...register('email', { required: 'Email is required', pattern: { value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, message: 'Invalid email address', }, })} placeholder="Email" /> {errors.email && <span>{errors.email.message}</span>} </div> <div> <input type="password" {...register('password', { required: 'Password is required', minLength: { value: 8, message: 'Password must be at least 8 characters', }, })} placeholder="Password" /> {errors.password && <span>{errors.password.message}</span>} </div> <div> <input type="number" {...register('age', { valueAsNumber: true, min: { value: 18, message: 'Must be 18 or older', }, })} placeholder="Age" /> {errors.age && <span>{errors.age.message}</span>} </div> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useForm, Controller } from 'react-hook-form';
import { TextField } from '@mui/material';
import Select from 'react-select'; interface FormData { email: string; country: { value: string; label: string } | null;
} function RHFWithControlled() { const { control, handleSubmit } = useForm<FormData>(); const countryOptions = [ { value: 'mx', label: 'Mexico' }, { value: 'us', label: 'United States' }, { value: 'ca', label: 'Canada' }, ]; return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <Controller name="email" control={control} rules={{ required: 'Email is required' }} render={({ field, fieldState: { error } }) => ( <TextField {...field} label="Email" error={!!error} helperText={error?.message} /> )} /> <Controller name="country" control={control} rules={{ required: 'Country is required' }} render={({ field }) => ( <Select {...field} options={countryOptions} placeholder="Select country" /> )} /> <button type="submit">Submit</button> </form> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useForm, Controller } from 'react-hook-form';
import { TextField } from '@mui/material';
import Select from 'react-select'; interface FormData { email: string; country: { value: string; label: string } | null;
} function RHFWithControlled() { const { control, handleSubmit } = useForm<FormData>(); const countryOptions = [ { value: 'mx', label: 'Mexico' }, { value: 'us', label: 'United States' }, { value: 'ca', label: 'Canada' }, ]; return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <Controller name="email" control={control} rules={{ required: 'Email is required' }} render={({ field, fieldState: { error } }) => ( <TextField {...field} label="Email" error={!!error} helperText={error?.message} /> )} /> <Controller name="country" control={control} rules={{ required: 'Country is required' }} render={({ field }) => ( <Select {...field} options={countryOptions} placeholder="Select country" /> )} /> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useForm, Controller } from 'react-hook-form';
import { TextField } from '@mui/material';
import Select from 'react-select'; interface FormData { email: string; country: { value: string; label: string } | null;
} function RHFWithControlled() { const { control, handleSubmit } = useForm<FormData>(); const countryOptions = [ { value: 'mx', label: 'Mexico' }, { value: 'us', label: 'United States' }, { value: 'ca', label: 'Canada' }, ]; return ( <form onSubmit={handleSubmit((data) => console.log(data))}> <Controller name="email" control={control} rules={{ required: 'Email is required' }} render={({ field, fieldState: { error } }) => ( <TextField {...field} label="Email" error={!!error} helperText={error?.message} /> )} /> <Controller name="country" control={control} rules={{ required: 'Country is required' }} render={({ field }) => ( <Select {...field} options={countryOptions} placeholder="Select country" /> )} /> <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup'; interface FormValues { email: string; password: string; age: number;
} const validationSchema = Yup.object({ email: Yup.string() .email('Invalid email address') .required('Email is required'), password: Yup.string() .min(8, 'Password must be at least 8 characters') .required('Password is required'), age: Yup.number() .min(18, 'Must be 18 or older') .required('Age is required'),
}); function FormikForm() { const initialValues: FormValues = { email: '', password: '', age: 0, }; return ( <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={(values) => { console.log(values); }} > {({ isSubmitting }) => ( <Form> <div> <label htmlFor="email">Email</label> <Field type="email" name="email" /> <ErrorMessage name="email" component="div" /> </div> <div> <label htmlFor="password">Password</label> <Field type="password" name="password" /> <ErrorMessage name="password" component="div" /> </div> <div> <label htmlFor="age">Age</label> <Field type="number" name="age" /> <ErrorMessage name="age" component="div" /> </div> <button type="submit" disabled={isSubmitting}> Submit </button> </Form> )} </Formik> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup'; interface FormValues { email: string; password: string; age: number;
} const validationSchema = Yup.object({ email: Yup.string() .email('Invalid email address') .required('Email is required'), password: Yup.string() .min(8, 'Password must be at least 8 characters') .required('Password is required'), age: Yup.number() .min(18, 'Must be 18 or older') .required('Age is required'),
}); function FormikForm() { const initialValues: FormValues = { email: '', password: '', age: 0, }; return ( <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={(values) => { console.log(values); }} > {({ isSubmitting }) => ( <Form> <div> <label htmlFor="email">Email</label> <Field type="email" name="email" /> <ErrorMessage name="email" component="div" /> </div> <div> <label htmlFor="password">Password</label> <Field type="password" name="password" /> <ErrorMessage name="password" component="div" /> </div> <div> <label htmlFor="age">Age</label> <Field type="number" name="age" /> <ErrorMessage name="age" component="div" /> </div> <button type="submit" disabled={isSubmitting}> Submit </button> </Form> )} </Formik> );
} COMMAND_BLOCK:
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup'; interface FormValues { email: string; password: string; age: number;
} const validationSchema = Yup.object({ email: Yup.string() .email('Invalid email address') .required('Email is required'), password: Yup.string() .min(8, 'Password must be at least 8 characters') .required('Password is required'), age: Yup.number() .min(18, 'Must be 18 or older') .required('Age is required'),
}); function FormikForm() { const initialValues: FormValues = { email: '', password: '', age: 0, }; return ( <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={(values) => { console.log(values); }} > {({ isSubmitting }) => ( <Form> <div> <label htmlFor="email">Email</label> <Field type="email" name="email" /> <ErrorMessage name="email" component="div" /> </div> <div> <label htmlFor="password">Password</label> <Field type="password" name="password" /> <ErrorMessage name="password" component="div" /> </div> <div> <label htmlFor="age">Age</label> <Field type="number" name="age" /> <ErrorMessage name="age" component="div" /> </div> <button type="submit" disabled={isSubmitting}> Submit </button> </Form> )} </Formik> );
} COMMAND_BLOCK:
import { useFormik } from 'formik'; function FormikHookForm() { const formik = useFormik<FormValues>({ initialValues: { email: '', password: '', age: 0, }, validationSchema, onSubmit: (values) => { console.log(values); }, }); return ( <form onSubmit={formik.handleSubmit}> <input type="email" {...formik.getFieldProps('email')} /> {formik.touched.email && formik.errors.email && ( <div>{formik.errors.email}</div> )} <input type="password" {...formik.getFieldProps('password')} /> {formik.touched.password && formik.errors.password && ( <div>{formik.errors.password}</div> )} <button type="submit">Submit</button> </form> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useFormik } from 'formik'; function FormikHookForm() { const formik = useFormik<FormValues>({ initialValues: { email: '', password: '', age: 0, }, validationSchema, onSubmit: (values) => { console.log(values); }, }); return ( <form onSubmit={formik.handleSubmit}> <input type="email" {...formik.getFieldProps('email')} /> {formik.touched.email && formik.errors.email && ( <div>{formik.errors.email}</div> )} <input type="password" {...formik.getFieldProps('password')} /> {formik.touched.password && formik.errors.password && ( <div>{formik.errors.password}</div> )} <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import { useFormik } from 'formik'; function FormikHookForm() { const formik = useFormik<FormValues>({ initialValues: { email: '', password: '', age: 0, }, validationSchema, onSubmit: (values) => { console.log(values); }, }); return ( <form onSubmit={formik.handleSubmit}> <input type="email" {...formik.getFieldProps('email')} /> {formik.touched.email && formik.errors.email && ( <div>{formik.errors.email}</div> )} <input type="password" {...formik.getFieldProps('password')} /> {formik.touched.password && formik.errors.password && ( <div>{formik.errors.password}</div> )} <button type="submit">Submit</button> </form> );
} COMMAND_BLOCK:
import * as Form from '@radix-ui/react-form'; function RadixUncontrolledForm() { return ( <Form.Root onSubmit={(e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); console.log(Object.fromEntries(formData)); }}> <Form.Field name="email"> <Form.Label>Email</Form.Label> <Form.Control asChild> <input type="email" defaultValue="" required /> </Form.Control> <Form.Message match="valueMissing"> Please enter your email </Form.Message> <Form.Message match="typeMismatch"> Please provide a valid email </Form.Message> </Form.Field> <Form.Submit>Submit</Form.Submit> </Form.Root> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import * as Form from '@radix-ui/react-form'; function RadixUncontrolledForm() { return ( <Form.Root onSubmit={(e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); console.log(Object.fromEntries(formData)); }}> <Form.Field name="email"> <Form.Label>Email</Form.Label> <Form.Control asChild> <input type="email" defaultValue="" required /> </Form.Control> <Form.Message match="valueMissing"> Please enter your email </Form.Message> <Form.Message match="typeMismatch"> Please provide a valid email </Form.Message> </Form.Field> <Form.Submit>Submit</Form.Submit> </Form.Root> );
} COMMAND_BLOCK:
import * as Form from '@radix-ui/react-form'; function RadixUncontrolledForm() { return ( <Form.Root onSubmit={(e) => { e.preventDefault(); const formData = new FormData(e.currentTarget); console.log(Object.fromEntries(formData)); }}> <Form.Field name="email"> <Form.Label>Email</Form.Label> <Form.Control asChild> <input type="email" defaultValue="" required /> </Form.Control> <Form.Message match="valueMissing"> Please enter your email </Form.Message> <Form.Message match="typeMismatch"> Please provide a valid email </Form.Message> </Form.Field> <Form.Submit>Submit</Form.Submit> </Form.Root> );
} COMMAND_BLOCK:
import { useState } from 'react';
import * as Form from '@radix-ui/react-form'; function RadixControlledForm() { const [email, setEmail] = useState(''); const [error, setError] = useState(''); const validateEmail = (value: string) => { if (!value) return 'Email is required'; if (!value.includes('@')) return 'Invalid email'; return ''; }; return ( <Form.Root onSubmit={(e) => { e.preventDefault(); const validationError = validateEmail(email); if (validationError) { setError(validationError); return; } console.log({ email }); }}> <Form.Field name="email"> <Form.Label>Email</Form.Label> <Form.Control asChild> <input type="email" value={email} onChange={(e) => { setEmail(e.target.value); setError(validateEmail(e.target.value)); }} /> </Form.Control> {error && <Form.Message>{error}</Form.Message>} </Form.Field> <Form.Submit>Submit</Form.Submit> </Form.Root> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState } from 'react';
import * as Form from '@radix-ui/react-form'; function RadixControlledForm() { const [email, setEmail] = useState(''); const [error, setError] = useState(''); const validateEmail = (value: string) => { if (!value) return 'Email is required'; if (!value.includes('@')) return 'Invalid email'; return ''; }; return ( <Form.Root onSubmit={(e) => { e.preventDefault(); const validationError = validateEmail(email); if (validationError) { setError(validationError); return; } console.log({ email }); }}> <Form.Field name="email"> <Form.Label>Email</Form.Label> <Form.Control asChild> <input type="email" value={email} onChange={(e) => { setEmail(e.target.value); setError(validateEmail(e.target.value)); }} /> </Form.Control> {error && <Form.Message>{error}</Form.Message>} </Form.Field> <Form.Submit>Submit</Form.Submit> </Form.Root> );
} COMMAND_BLOCK:
import { useState } from 'react';
import * as Form from '@radix-ui/react-form'; function RadixControlledForm() { const [email, setEmail] = useState(''); const [error, setError] = useState(''); const validateEmail = (value: string) => { if (!value) return 'Email is required'; if (!value.includes('@')) return 'Invalid email'; return ''; }; return ( <Form.Root onSubmit={(e) => { e.preventDefault(); const validationError = validateEmail(email); if (validationError) { setError(validationError); return; } console.log({ email }); }}> <Form.Field name="email"> <Form.Label>Email</Form.Label> <Form.Control asChild> <input type="email" value={email} onChange={(e) => { setEmail(e.target.value); setError(validateEmail(e.target.value)); }} /> </Form.Control> {error && <Form.Message>{error}</Form.Message>} </Form.Field> <Form.Submit>Submit</Form.Submit> </Form.Root> );
} COMMAND_BLOCK:
const getButtonProps = (userProps = {}) => ({ 'aria-pressed': isPressed, onClick: composeHandlers(userProps.onClick, internalClickHandler), ...userProps,
}) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
const getButtonProps = (userProps = {}) => ({ 'aria-pressed': isPressed, onClick: composeHandlers(userProps.onClick, internalClickHandler), ...userProps,
}) COMMAND_BLOCK:
const getButtonProps = (userProps = {}) => ({ 'aria-pressed': isPressed, onClick: composeHandlers(userProps.onClick, internalClickHandler), ...userProps,
}) COMMAND_BLOCK:
export function callAll<Args extends any[]>( ...fns: Array<((...args: Args) => void) | undefined>
) { return (...args: Args) => { fns.forEach((fn) => fn?.(...args)); };
} export function mergeRefs<T>(...refs: Array<React.Ref<T> | undefined>) { return (value: T) => { refs.forEach((ref) => { if (typeof ref === 'function') { ref(value); } else if (ref != null) { (ref as React.MutableRefObject<T | null>).current = value; } }); };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
export function callAll<Args extends any[]>( ...fns: Array<((...args: Args) => void) | undefined>
) { return (...args: Args) => { fns.forEach((fn) => fn?.(...args)); };
} export function mergeRefs<T>(...refs: Array<React.Ref<T> | undefined>) { return (value: T) => { refs.forEach((ref) => { if (typeof ref === 'function') { ref(value); } else if (ref != null) { (ref as React.MutableRefObject<T | null>).current = value; } }); };
} COMMAND_BLOCK:
export function callAll<Args extends any[]>( ...fns: Array<((...args: Args) => void) | undefined>
) { return (...args: Args) => { fns.forEach((fn) => fn?.(...args)); };
} export function mergeRefs<T>(...refs: Array<React.Ref<T> | undefined>) { return (value: T) => { refs.forEach((ref) => { if (typeof ref === 'function') { ref(value); } else if (ref != null) { (ref as React.MutableRefObject<T | null>).current = value; } }); };
} COMMAND_BLOCK:
import { useState, useRef, useEffect, MouseEvent, KeyboardEvent } from 'react';
import { callAll, mergeRefs } from './utils'; interface Item { id: string; label: string; disabled?: boolean;
} interface UseDropdownProps { items: Item[]; onSelect?: (item: Item) => void;
} interface GetToggleButtonPropsOptions { onClick?: (e: MouseEvent<HTMLButtonElement>) => void; ref?: React.Ref<HTMLButtonElement>; disabled?: boolean; [key: string]: any;
} interface GetMenuPropsOptions { ref?: React.Ref<HTMLDivElement>; [key: string]: any;
} interface GetItemPropsOptions { item: Item; index: number; onClick?: (e: MouseEvent<HTMLDivElement>) => void; ref?: React.Ref<HTMLDivElement>; disabled?: boolean; [key: string]: any;
} export function useDropdown({ items, onSelect }: UseDropdownProps) { const [isOpen, setIsOpen] = useState(false); const [highlightedIndex, setHighlightedIndex] = useState(0); const [selectedItem, setSelectedItem] = useState<Item | null>(null); const menuRef = useRef<HTMLDivElement>(null); const toggleRef = useRef<HTMLButtonElement>(null); useEffect(() => { if (!isOpen) return; const handleClickOutside = (e: Event) => { if ( menuRef.current && !menuRef.current.contains(e.target as Node) && toggleRef.current && !toggleRef.current.contains(e.target as Node) ) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen]); const handleKeyDown = (e: KeyboardEvent) => { if (!isOpen) return; switch (e.key) { case 'ArrowDown': e.preventDefault(); setHighlightedIndex((i) => i < items.length - 1 ? i + 1 : i ); break; case 'ArrowUp': e.preventDefault(); setHighlightedIndex((i) => (i > 0 ? i - 1 : i)); break; case 'Enter': e.preventDefault(); if (items[highlightedIndex] && !items[highlightedIndex].disabled) { selectItem(items[highlightedIndex]); } break; case 'Escape': setIsOpen(false); toggleRef.current?.focus(); break; } }; const selectItem = (item: Item) => { setSelectedItem(item); setIsOpen(false); onSelect?.(item); toggleRef.current?.focus(); }; const getToggleButtonProps = ({ onClick, ref, disabled, ...props }: GetToggleButtonPropsOptions = {}) => ({ ref: mergeRefs(toggleRef, ref), 'aria-haspopup': 'listbox' as const, 'aria-expanded': isOpen, disabled, onClick: callAll(onClick, () => !disabled && setIsOpen(!isOpen)), onKeyDown: handleKeyDown, ...props, }); const getMenuProps = ({ ref, ...props }: GetMenuPropsOptions = {}) => ({ ref: mergeRefs(menuRef, ref), role: 'listbox' as const, style: { display: isOpen ? 'block' : 'none', ...props.style, }, ...props, }); const getItemProps = ({ item, index, onClick, ref, disabled, ...props }: GetItemPropsOptions) => ({ ref, role: 'option' as const, 'aria-selected': highlightedIndex === index, 'aria-disabled': disabled || item.disabled, onClick: callAll( onClick, () => { if (!disabled && !item.disabled) { selectItem(item); } } ), onMouseEnter: () => { if (!disabled && !item.disabled) { setHighlightedIndex(index); } }, style: { backgroundColor: highlightedIndex === index ? '#e0e0e0' : 'white', cursor: disabled || item.disabled ? 'not-allowed' : 'pointer', ...props.style, }, ...props, }); return { isOpen, highlightedIndex, selectedItem, getToggleButtonProps, getMenuProps, getItemProps, };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, useRef, useEffect, MouseEvent, KeyboardEvent } from 'react';
import { callAll, mergeRefs } from './utils'; interface Item { id: string; label: string; disabled?: boolean;
} interface UseDropdownProps { items: Item[]; onSelect?: (item: Item) => void;
} interface GetToggleButtonPropsOptions { onClick?: (e: MouseEvent<HTMLButtonElement>) => void; ref?: React.Ref<HTMLButtonElement>; disabled?: boolean; [key: string]: any;
} interface GetMenuPropsOptions { ref?: React.Ref<HTMLDivElement>; [key: string]: any;
} interface GetItemPropsOptions { item: Item; index: number; onClick?: (e: MouseEvent<HTMLDivElement>) => void; ref?: React.Ref<HTMLDivElement>; disabled?: boolean; [key: string]: any;
} export function useDropdown({ items, onSelect }: UseDropdownProps) { const [isOpen, setIsOpen] = useState(false); const [highlightedIndex, setHighlightedIndex] = useState(0); const [selectedItem, setSelectedItem] = useState<Item | null>(null); const menuRef = useRef<HTMLDivElement>(null); const toggleRef = useRef<HTMLButtonElement>(null); useEffect(() => { if (!isOpen) return; const handleClickOutside = (e: Event) => { if ( menuRef.current && !menuRef.current.contains(e.target as Node) && toggleRef.current && !toggleRef.current.contains(e.target as Node) ) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen]); const handleKeyDown = (e: KeyboardEvent) => { if (!isOpen) return; switch (e.key) { case 'ArrowDown': e.preventDefault(); setHighlightedIndex((i) => i < items.length - 1 ? i + 1 : i ); break; case 'ArrowUp': e.preventDefault(); setHighlightedIndex((i) => (i > 0 ? i - 1 : i)); break; case 'Enter': e.preventDefault(); if (items[highlightedIndex] && !items[highlightedIndex].disabled) { selectItem(items[highlightedIndex]); } break; case 'Escape': setIsOpen(false); toggleRef.current?.focus(); break; } }; const selectItem = (item: Item) => { setSelectedItem(item); setIsOpen(false); onSelect?.(item); toggleRef.current?.focus(); }; const getToggleButtonProps = ({ onClick, ref, disabled, ...props }: GetToggleButtonPropsOptions = {}) => ({ ref: mergeRefs(toggleRef, ref), 'aria-haspopup': 'listbox' as const, 'aria-expanded': isOpen, disabled, onClick: callAll(onClick, () => !disabled && setIsOpen(!isOpen)), onKeyDown: handleKeyDown, ...props, }); const getMenuProps = ({ ref, ...props }: GetMenuPropsOptions = {}) => ({ ref: mergeRefs(menuRef, ref), role: 'listbox' as const, style: { display: isOpen ? 'block' : 'none', ...props.style, }, ...props, }); const getItemProps = ({ item, index, onClick, ref, disabled, ...props }: GetItemPropsOptions) => ({ ref, role: 'option' as const, 'aria-selected': highlightedIndex === index, 'aria-disabled': disabled || item.disabled, onClick: callAll( onClick, () => { if (!disabled && !item.disabled) { selectItem(item); } } ), onMouseEnter: () => { if (!disabled && !item.disabled) { setHighlightedIndex(index); } }, style: { backgroundColor: highlightedIndex === index ? '#e0e0e0' : 'white', cursor: disabled || item.disabled ? 'not-allowed' : 'pointer', ...props.style, }, ...props, }); return { isOpen, highlightedIndex, selectedItem, getToggleButtonProps, getMenuProps, getItemProps, };
} COMMAND_BLOCK:
import { useState, useRef, useEffect, MouseEvent, KeyboardEvent } from 'react';
import { callAll, mergeRefs } from './utils'; interface Item { id: string; label: string; disabled?: boolean;
} interface UseDropdownProps { items: Item[]; onSelect?: (item: Item) => void;
} interface GetToggleButtonPropsOptions { onClick?: (e: MouseEvent<HTMLButtonElement>) => void; ref?: React.Ref<HTMLButtonElement>; disabled?: boolean; [key: string]: any;
} interface GetMenuPropsOptions { ref?: React.Ref<HTMLDivElement>; [key: string]: any;
} interface GetItemPropsOptions { item: Item; index: number; onClick?: (e: MouseEvent<HTMLDivElement>) => void; ref?: React.Ref<HTMLDivElement>; disabled?: boolean; [key: string]: any;
} export function useDropdown({ items, onSelect }: UseDropdownProps) { const [isOpen, setIsOpen] = useState(false); const [highlightedIndex, setHighlightedIndex] = useState(0); const [selectedItem, setSelectedItem] = useState<Item | null>(null); const menuRef = useRef<HTMLDivElement>(null); const toggleRef = useRef<HTMLButtonElement>(null); useEffect(() => { if (!isOpen) return; const handleClickOutside = (e: Event) => { if ( menuRef.current && !menuRef.current.contains(e.target as Node) && toggleRef.current && !toggleRef.current.contains(e.target as Node) ) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen]); const handleKeyDown = (e: KeyboardEvent) => { if (!isOpen) return; switch (e.key) { case 'ArrowDown': e.preventDefault(); setHighlightedIndex((i) => i < items.length - 1 ? i + 1 : i ); break; case 'ArrowUp': e.preventDefault(); setHighlightedIndex((i) => (i > 0 ? i - 1 : i)); break; case 'Enter': e.preventDefault(); if (items[highlightedIndex] && !items[highlightedIndex].disabled) { selectItem(items[highlightedIndex]); } break; case 'Escape': setIsOpen(false); toggleRef.current?.focus(); break; } }; const selectItem = (item: Item) => { setSelectedItem(item); setIsOpen(false); onSelect?.(item); toggleRef.current?.focus(); }; const getToggleButtonProps = ({ onClick, ref, disabled, ...props }: GetToggleButtonPropsOptions = {}) => ({ ref: mergeRefs(toggleRef, ref), 'aria-haspopup': 'listbox' as const, 'aria-expanded': isOpen, disabled, onClick: callAll(onClick, () => !disabled && setIsOpen(!isOpen)), onKeyDown: handleKeyDown, ...props, }); const getMenuProps = ({ ref, ...props }: GetMenuPropsOptions = {}) => ({ ref: mergeRefs(menuRef, ref), role: 'listbox' as const, style: { display: isOpen ? 'block' : 'none', ...props.style, }, ...props, }); const getItemProps = ({ item, index, onClick, ref, disabled, ...props }: GetItemPropsOptions) => ({ ref, role: 'option' as const, 'aria-selected': highlightedIndex === index, 'aria-disabled': disabled || item.disabled, onClick: callAll( onClick, () => { if (!disabled && !item.disabled) { selectItem(item); } } ), onMouseEnter: () => { if (!disabled && !item.disabled) { setHighlightedIndex(index); } }, style: { backgroundColor: highlightedIndex === index ? '#e0e0e0' : 'white', cursor: disabled || item.disabled ? 'not-allowed' : 'pointer', ...props.style, }, ...props, }); return { isOpen, highlightedIndex, selectedItem, getToggleButtonProps, getMenuProps, getItemProps, };
} COMMAND_BLOCK:
import { useState, useRef, useEffect, MouseEvent, FocusEvent } from 'react';
import { callAll, mergeRefs } from './utils'; interface UseTooltipProps { delay?: number; placement?: 'top' | 'bottom' | 'left' | 'right';
} interface GetTriggerPropsOptions { onMouseEnter?: (e: MouseEvent<HTMLElement>) => void; onMouseLeave?: (e: MouseEvent<HTMLElement>) => void; onFocus?: (e: FocusEvent<HTMLElement>) => void; onBlur?: (e: FocusEvent<HTMLElement>) => void; ref?: React.Ref<HTMLElement>; [key: string]: any;
} interface GetTooltipPropsOptions { ref?: React.Ref<HTMLDivElement>; [key: string]: any;
} export function useTooltip({ delay = 200, placement = 'top',
}: UseTooltipProps = {}) { const [isVisible, setIsVisible] = useState(false); const timeoutRef = useRef<NodeJS.Timeout>(); const triggerRef = useRef<HTMLElement>(null); const tooltipRef = useRef<HTMLDivElement>(null); const show = () => { clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => { setIsVisible(true); }, delay); }; const hide = () => { clearTimeout(timeoutRef.current); setIsVisible(false); }; useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); const getTriggerProps = ({ onMouseEnter, onMouseLeave, onFocus, onBlur, ref, ...props }: GetTriggerPropsOptions = {}) => ({ ref: mergeRefs(triggerRef, ref), 'aria-describedby': isVisible ? 'tooltip' : undefined, onMouseEnter: callAll(onMouseEnter, show), onMouseLeave: callAll(onMouseLeave, hide), onFocus: callAll(onFocus, show), onBlur: callAll(onBlur, hide), ...props, }); const getTooltipProps = ({ ref, ...props }: GetTooltipPropsOptions = {}) => ({ ref: mergeRefs(tooltipRef, ref), id: 'tooltip', role: 'tooltip' as const, style: { visibility: isVisible ? ('visible' as const) : ('hidden' as const), position: 'absolute' as const, ...props.style, }, ...props, }); return { isVisible, placement, getTriggerProps, getTooltipProps, };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, useRef, useEffect, MouseEvent, FocusEvent } from 'react';
import { callAll, mergeRefs } from './utils'; interface UseTooltipProps { delay?: number; placement?: 'top' | 'bottom' | 'left' | 'right';
} interface GetTriggerPropsOptions { onMouseEnter?: (e: MouseEvent<HTMLElement>) => void; onMouseLeave?: (e: MouseEvent<HTMLElement>) => void; onFocus?: (e: FocusEvent<HTMLElement>) => void; onBlur?: (e: FocusEvent<HTMLElement>) => void; ref?: React.Ref<HTMLElement>; [key: string]: any;
} interface GetTooltipPropsOptions { ref?: React.Ref<HTMLDivElement>; [key: string]: any;
} export function useTooltip({ delay = 200, placement = 'top',
}: UseTooltipProps = {}) { const [isVisible, setIsVisible] = useState(false); const timeoutRef = useRef<NodeJS.Timeout>(); const triggerRef = useRef<HTMLElement>(null); const tooltipRef = useRef<HTMLDivElement>(null); const show = () => { clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => { setIsVisible(true); }, delay); }; const hide = () => { clearTimeout(timeoutRef.current); setIsVisible(false); }; useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); const getTriggerProps = ({ onMouseEnter, onMouseLeave, onFocus, onBlur, ref, ...props }: GetTriggerPropsOptions = {}) => ({ ref: mergeRefs(triggerRef, ref), 'aria-describedby': isVisible ? 'tooltip' : undefined, onMouseEnter: callAll(onMouseEnter, show), onMouseLeave: callAll(onMouseLeave, hide), onFocus: callAll(onFocus, show), onBlur: callAll(onBlur, hide), ...props, }); const getTooltipProps = ({ ref, ...props }: GetTooltipPropsOptions = {}) => ({ ref: mergeRefs(tooltipRef, ref), id: 'tooltip', role: 'tooltip' as const, style: { visibility: isVisible ? ('visible' as const) : ('hidden' as const), position: 'absolute' as const, ...props.style, }, ...props, }); return { isVisible, placement, getTriggerProps, getTooltipProps, };
} COMMAND_BLOCK:
import { useState, useRef, useEffect, MouseEvent, FocusEvent } from 'react';
import { callAll, mergeRefs } from './utils'; interface UseTooltipProps { delay?: number; placement?: 'top' | 'bottom' | 'left' | 'right';
} interface GetTriggerPropsOptions { onMouseEnter?: (e: MouseEvent<HTMLElement>) => void; onMouseLeave?: (e: MouseEvent<HTMLElement>) => void; onFocus?: (e: FocusEvent<HTMLElement>) => void; onBlur?: (e: FocusEvent<HTMLElement>) => void; ref?: React.Ref<HTMLElement>; [key: string]: any;
} interface GetTooltipPropsOptions { ref?: React.Ref<HTMLDivElement>; [key: string]: any;
} export function useTooltip({ delay = 200, placement = 'top',
}: UseTooltipProps = {}) { const [isVisible, setIsVisible] = useState(false); const timeoutRef = useRef<NodeJS.Timeout>(); const triggerRef = useRef<HTMLElement>(null); const tooltipRef = useRef<HTMLDivElement>(null); const show = () => { clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => { setIsVisible(true); }, delay); }; const hide = () => { clearTimeout(timeoutRef.current); setIsVisible(false); }; useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, []); const getTriggerProps = ({ onMouseEnter, onMouseLeave, onFocus, onBlur, ref, ...props }: GetTriggerPropsOptions = {}) => ({ ref: mergeRefs(triggerRef, ref), 'aria-describedby': isVisible ? 'tooltip' : undefined, onMouseEnter: callAll(onMouseEnter, show), onMouseLeave: callAll(onMouseLeave, hide), onFocus: callAll(onFocus, show), onBlur: callAll(onBlur, hide), ...props, }); const getTooltipProps = ({ ref, ...props }: GetTooltipPropsOptions = {}) => ({ ref: mergeRefs(tooltipRef, ref), id: 'tooltip', role: 'tooltip' as const, style: { visibility: isVisible ? ('visible' as const) : ('hidden' as const), position: 'absolute' as const, ...props.style, }, ...props, }); return { isVisible, placement, getTriggerProps, getTooltipProps, };
} COMMAND_BLOCK:
import { useState, MouseEvent, KeyboardEvent } from 'react';
import { callAll } from './utils'; interface UseAccordionProps { allowMultiple?: boolean; defaultExpandedIndexes?: number[];
} interface GetButtonPropsOptions { index: number; onClick?: (e: MouseEvent<HTMLButtonElement>) => void; disabled?: boolean; [key: string]: any;
} interface GetPanelPropsOptions { index: number; [key: string]: any;
} export function useAccordion({ allowMultiple = false, defaultExpandedIndexes = [],
}: UseAccordionProps = {}) { const [expandedIndexes, setExpandedIndexes] = useState<Set<number>>( new Set(defaultExpandedIndexes) ); const toggleItem = (index: number) => { setExpandedIndexes((prev) => { const next = new Set(prev); if (next.has(index)) { next.delete(index); } else { if (!allowMultiple) { next.clear(); } next.add(index); } return next; }); }; const getButtonProps = ({ index, onClick, disabled, ...props }: GetButtonPropsOptions) => { const isExpanded = expandedIndexes.has(index); return { type: 'button' as const, 'aria-expanded': isExpanded, 'aria-controls': `panel-${index}`, id: `button-${index}`, disabled, onClick: callAll(onClick, () => { if (!disabled) { toggleItem(index); } }), ...props, }; }; const getPanelProps = ({ index, ...props }: GetPanelPropsOptions) => { const isExpanded = expandedIndexes.has(index); return { id: `panel-${index}`, role: 'region' as const, 'aria-labelledby': `button-${index}`, hidden: !isExpanded, ...props, }; }; return { expandedIndexes, getButtonProps, getPanelProps, };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, MouseEvent, KeyboardEvent } from 'react';
import { callAll } from './utils'; interface UseAccordionProps { allowMultiple?: boolean; defaultExpandedIndexes?: number[];
} interface GetButtonPropsOptions { index: number; onClick?: (e: MouseEvent<HTMLButtonElement>) => void; disabled?: boolean; [key: string]: any;
} interface GetPanelPropsOptions { index: number; [key: string]: any;
} export function useAccordion({ allowMultiple = false, defaultExpandedIndexes = [],
}: UseAccordionProps = {}) { const [expandedIndexes, setExpandedIndexes] = useState<Set<number>>( new Set(defaultExpandedIndexes) ); const toggleItem = (index: number) => { setExpandedIndexes((prev) => { const next = new Set(prev); if (next.has(index)) { next.delete(index); } else { if (!allowMultiple) { next.clear(); } next.add(index); } return next; }); }; const getButtonProps = ({ index, onClick, disabled, ...props }: GetButtonPropsOptions) => { const isExpanded = expandedIndexes.has(index); return { type: 'button' as const, 'aria-expanded': isExpanded, 'aria-controls': `panel-${index}`, id: `button-${index}`, disabled, onClick: callAll(onClick, () => { if (!disabled) { toggleItem(index); } }), ...props, }; }; const getPanelProps = ({ index, ...props }: GetPanelPropsOptions) => { const isExpanded = expandedIndexes.has(index); return { id: `panel-${index}`, role: 'region' as const, 'aria-labelledby': `button-${index}`, hidden: !isExpanded, ...props, }; }; return { expandedIndexes, getButtonProps, getPanelProps, };
} COMMAND_BLOCK:
import { useState, MouseEvent, KeyboardEvent } from 'react';
import { callAll } from './utils'; interface UseAccordionProps { allowMultiple?: boolean; defaultExpandedIndexes?: number[];
} interface GetButtonPropsOptions { index: number; onClick?: (e: MouseEvent<HTMLButtonElement>) => void; disabled?: boolean; [key: string]: any;
} interface GetPanelPropsOptions { index: number; [key: string]: any;
} export function useAccordion({ allowMultiple = false, defaultExpandedIndexes = [],
}: UseAccordionProps = {}) { const [expandedIndexes, setExpandedIndexes] = useState<Set<number>>( new Set(defaultExpandedIndexes) ); const toggleItem = (index: number) => { setExpandedIndexes((prev) => { const next = new Set(prev); if (next.has(index)) { next.delete(index); } else { if (!allowMultiple) { next.clear(); } next.add(index); } return next; }); }; const getButtonProps = ({ index, onClick, disabled, ...props }: GetButtonPropsOptions) => { const isExpanded = expandedIndexes.has(index); return { type: 'button' as const, 'aria-expanded': isExpanded, 'aria-controls': `panel-${index}`, id: `button-${index}`, disabled, onClick: callAll(onClick, () => { if (!disabled) { toggleItem(index); } }), ...props, }; }; const getPanelProps = ({ index, ...props }: GetPanelPropsOptions) => { const isExpanded = expandedIndexes.has(index); return { id: `panel-${index}`, role: 'region' as const, 'aria-labelledby': `button-${index}`, hidden: !isExpanded, ...props, }; }; return { expandedIndexes, getButtonProps, getPanelProps, };
} COMMAND_BLOCK:
import { useDropdown } from './useDropdown';
import { useTooltip } from './useTooltip';
import { useAccordion } from './useAccordion'; function DropdownExample() { const items = [ { id: '1', label: 'Option 1' }, { id: '2', label: 'Option 2' }, { id: '3', label: 'Option 3', disabled: true }, { id: '4', label: 'Option 4' }, ]; const { selectedItem, getToggleButtonProps, getMenuProps, getItemProps, } = useDropdown({ items, onSelect: (item) => console.log('Selected:', item), }); return ( <div> <h2>Dropdown with Props Getters</h2> <button {...getToggleButtonProps({ className: 'custom-button', onClick: () => console.log('Custom click handler'), })} > {selectedItem ? selectedItem.label : 'Select an option'} </button> <div {...getMenuProps({ className: 'custom-menu' })}> {items.map((item, index) => ( <div key={item.id} {...getItemProps({ item, index, className: 'custom-item', })} > {item.label} </div> ))} </div> </div> );
} function TooltipExample() { const { getTriggerProps, getTooltipProps } = useTooltip({ delay: 300, }); return ( <div> <h2>Tooltip with Props Getters</h2> <button {...getTriggerProps({ className: 'trigger-button', onMouseEnter: () => console.log('Custom mouse enter'), })} > Hover me </button> <div {...getTooltipProps({ className: 'custom-tooltip', })} > This is the tooltip content </div> </div> );
} function AccordionExample() { const { getButtonProps, getPanelProps } = useAccordion({ allowMultiple: true, defaultExpandedIndexes: [0], }); const items = [ { title: 'Section 1', content: 'Content for section 1' }, { title: 'Section 2', content: 'Content for section 2' }, { title: 'Section 3', content: 'Content for section 3' }, ]; return ( <div> <h2>Accordion with Props Getters</h2> {items.map((item, index) => ( <div key={index}> <button {...getButtonProps({ index, className: 'accordion-button', })} > {item.title} </button> <div {...getPanelProps({ index, className: 'accordion-panel', })} > {item.content} </div> </div> ))} </div> );
} export default function App() { return ( <div> <DropdownExample /> <TooltipExample /> <AccordionExample /> </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useDropdown } from './useDropdown';
import { useTooltip } from './useTooltip';
import { useAccordion } from './useAccordion'; function DropdownExample() { const items = [ { id: '1', label: 'Option 1' }, { id: '2', label: 'Option 2' }, { id: '3', label: 'Option 3', disabled: true }, { id: '4', label: 'Option 4' }, ]; const { selectedItem, getToggleButtonProps, getMenuProps, getItemProps, } = useDropdown({ items, onSelect: (item) => console.log('Selected:', item), }); return ( <div> <h2>Dropdown with Props Getters</h2> <button {...getToggleButtonProps({ className: 'custom-button', onClick: () => console.log('Custom click handler'), })} > {selectedItem ? selectedItem.label : 'Select an option'} </button> <div {...getMenuProps({ className: 'custom-menu' })}> {items.map((item, index) => ( <div key={item.id} {...getItemProps({ item, index, className: 'custom-item', })} > {item.label} </div> ))} </div> </div> );
} function TooltipExample() { const { getTriggerProps, getTooltipProps } = useTooltip({ delay: 300, }); return ( <div> <h2>Tooltip with Props Getters</h2> <button {...getTriggerProps({ className: 'trigger-button', onMouseEnter: () => console.log('Custom mouse enter'), })} > Hover me </button> <div {...getTooltipProps({ className: 'custom-tooltip', })} > This is the tooltip content </div> </div> );
} function AccordionExample() { const { getButtonProps, getPanelProps } = useAccordion({ allowMultiple: true, defaultExpandedIndexes: [0], }); const items = [ { title: 'Section 1', content: 'Content for section 1' }, { title: 'Section 2', content: 'Content for section 2' }, { title: 'Section 3', content: 'Content for section 3' }, ]; return ( <div> <h2>Accordion with Props Getters</h2> {items.map((item, index) => ( <div key={index}> <button {...getButtonProps({ index, className: 'accordion-button', })} > {item.title} </button> <div {...getPanelProps({ index, className: 'accordion-panel', })} > {item.content} </div> </div> ))} </div> );
} export default function App() { return ( <div> <DropdownExample /> <TooltipExample /> <AccordionExample /> </div> );
} COMMAND_BLOCK:
import { useDropdown } from './useDropdown';
import { useTooltip } from './useTooltip';
import { useAccordion } from './useAccordion'; function DropdownExample() { const items = [ { id: '1', label: 'Option 1' }, { id: '2', label: 'Option 2' }, { id: '3', label: 'Option 3', disabled: true }, { id: '4', label: 'Option 4' }, ]; const { selectedItem, getToggleButtonProps, getMenuProps, getItemProps, } = useDropdown({ items, onSelect: (item) => console.log('Selected:', item), }); return ( <div> <h2>Dropdown with Props Getters</h2> <button {...getToggleButtonProps({ className: 'custom-button', onClick: () => console.log('Custom click handler'), })} > {selectedItem ? selectedItem.label : 'Select an option'} </button> <div {...getMenuProps({ className: 'custom-menu' })}> {items.map((item, index) => ( <div key={item.id} {...getItemProps({ item, index, className: 'custom-item', })} > {item.label} </div> ))} </div> </div> );
} function TooltipExample() { const { getTriggerProps, getTooltipProps } = useTooltip({ delay: 300, }); return ( <div> <h2>Tooltip with Props Getters</h2> <button {...getTriggerProps({ className: 'trigger-button', onMouseEnter: () => console.log('Custom mouse enter'), })} > Hover me </button> <div {...getTooltipProps({ className: 'custom-tooltip', })} > This is the tooltip content </div> </div> );
} function AccordionExample() { const { getButtonProps, getPanelProps } = useAccordion({ allowMultiple: true, defaultExpandedIndexes: [0], }); const items = [ { title: 'Section 1', content: 'Content for section 1' }, { title: 'Section 2', content: 'Content for section 2' }, { title: 'Section 3', content: 'Content for section 3' }, ]; return ( <div> <h2>Accordion with Props Getters</h2> {items.map((item, index) => ( <div key={index}> <button {...getButtonProps({ index, className: 'accordion-button', })} > {item.title} </button> <div {...getPanelProps({ index, className: 'accordion-panel', })} > {item.content} </div> </div> ))} </div> );
} export default function App() { return ( <div> <DropdownExample /> <TooltipExample /> <AccordionExample /> </div> );
} COMMAND_BLOCK:
import { useSelect } from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps, } = useSelect({ items, itemToString: (item) => (item ? item.value : ''), onSelectedItemChange: ({ selectedItem }) => { console.log('Selected:', selectedItem); }, }); return ( <div> <label {...getLabelProps()}>Select a fruit</label> <button type="button" {...getToggleButtonProps()}> {selectedItem ? selectedItem.value : 'Select...'} </button> <ul {...getMenuProps()}> {isOpen && items.map((item, index) => ( <li key={item.id} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }} > {item.value} </li> ))} </ul> </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useSelect } from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps, } = useSelect({ items, itemToString: (item) => (item ? item.value : ''), onSelectedItemChange: ({ selectedItem }) => { console.log('Selected:', selectedItem); }, }); return ( <div> <label {...getLabelProps()}>Select a fruit</label> <button type="button" {...getToggleButtonProps()}> {selectedItem ? selectedItem.value : 'Select...'} </button> <ul {...getMenuProps()}> {isOpen && items.map((item, index) => ( <li key={item.id} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }} > {item.value} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useSelect } from 'downshift'; interface Item { id: string; value: string;
} function Select() { const items: Item[] = [ { id: '1', value: 'Apple' }, { id: '2', value: 'Banana' }, { id: '3', value: 'Cherry' }, ]; const { isOpen, selectedItem, getToggleButtonProps, getLabelProps, getMenuProps, highlightedIndex, getItemProps, } = useSelect({ items, itemToString: (item) => (item ? item.value : ''), onSelectedItemChange: ({ selectedItem }) => { console.log('Selected:', selectedItem); }, }); return ( <div> <label {...getLabelProps()}>Select a fruit</label> <button type="button" {...getToggleButtonProps()}> {selectedItem ? selectedItem.value : 'Select...'} </button> <ul {...getMenuProps()}> {isOpen && items.map((item, index) => ( <li key={item.id} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }} > {item.value} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useCombobox } from 'downshift';
import { useState } from 'react'; function Combobox() { const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; const [inputItems, setInputItems] = useState(items); const { isOpen, getToggleButtonProps, getLabelProps, getMenuProps, getInputProps, highlightedIndex, getItemProps, } = useCombobox({ items: inputItems, onInputValueChange: ({ inputValue }) => { setInputItems( items.filter((item) => item.toLowerCase().includes(inputValue?.toLowerCase() || '') ) ); }, }); return ( <div> <label {...getLabelProps()}>Choose a fruit:</label> <div> <input {...getInputProps()} /> <button type="button" {...getToggleButtonProps()}> ↓ </button> </div> <ul {...getMenuProps()}> {isOpen && inputItems.map((item, index) => ( <li key={`${item}${index}`} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? '#bde4ff' : '', }} > {item} </li> ))} </ul> </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useCombobox } from 'downshift';
import { useState } from 'react'; function Combobox() { const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; const [inputItems, setInputItems] = useState(items); const { isOpen, getToggleButtonProps, getLabelProps, getMenuProps, getInputProps, highlightedIndex, getItemProps, } = useCombobox({ items: inputItems, onInputValueChange: ({ inputValue }) => { setInputItems( items.filter((item) => item.toLowerCase().includes(inputValue?.toLowerCase() || '') ) ); }, }); return ( <div> <label {...getLabelProps()}>Choose a fruit:</label> <div> <input {...getInputProps()} /> <button type="button" {...getToggleButtonProps()}> ↓ </button> </div> <ul {...getMenuProps()}> {isOpen && inputItems.map((item, index) => ( <li key={`${item}${index}`} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? '#bde4ff' : '', }} > {item} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useCombobox } from 'downshift';
import { useState } from 'react'; function Combobox() { const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; const [inputItems, setInputItems] = useState(items); const { isOpen, getToggleButtonProps, getLabelProps, getMenuProps, getInputProps, highlightedIndex, getItemProps, } = useCombobox({ items: inputItems, onInputValueChange: ({ inputValue }) => { setInputItems( items.filter((item) => item.toLowerCase().includes(inputValue?.toLowerCase() || '') ) ); }, }); return ( <div> <label {...getLabelProps()}>Choose a fruit:</label> <div> <input {...getInputProps()} /> <button type="button" {...getToggleButtonProps()}> ↓ </button> </div> <ul {...getMenuProps()}> {isOpen && inputItems.map((item, index) => ( <li key={`${item}${index}`} {...getItemProps({ item, index })} style={{ backgroundColor: highlightedIndex === index ? '#bde4ff' : '', }} > {item} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useButton } from '@react-aria/button';
import { useTooltip, useTooltipTrigger } from '@react-aria/tooltip';
import { useOverlayPosition } from '@react-aria/overlays';
import { useRef, useState } from 'react';
import type { AriaButtonProps } from '@react-types/button';
import type { TooltipTriggerState } from '@react-stately/tooltip'; function TooltipButton(props: AriaButtonProps) { const ref = useRef<HTMLButtonElement>(null); const { buttonProps } = useButton(props, ref); return ( <button {...buttonProps} ref={ref}> {props.children} </button> );
} function TooltipWithTrigger() { const state: TooltipTriggerState = { isOpen: false, open: () => {}, close: () => {}, }; const triggerRef = useRef<HTMLButtonElement>(null); const tooltipRef = useRef<HTMLDivElement>(null); const { triggerProps, tooltipProps } = useTooltipTrigger( { delay: 300 }, state, triggerRef ); const { tooltipProps: positionProps } = useTooltip( tooltipProps, state ); const { overlayProps } = useOverlayPosition({ targetRef: triggerRef, overlayRef: tooltipRef, placement: 'top', offset: 5, isOpen: state.isOpen, }); return ( <div> <button {...triggerProps} ref={triggerRef}> Hover me </button> {state.isOpen && ( <div {...positionProps} {...overlayProps} ref={tooltipRef} > Tooltip content </div> )} </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useButton } from '@react-aria/button';
import { useTooltip, useTooltipTrigger } from '@react-aria/tooltip';
import { useOverlayPosition } from '@react-aria/overlays';
import { useRef, useState } from 'react';
import type { AriaButtonProps } from '@react-types/button';
import type { TooltipTriggerState } from '@react-stately/tooltip'; function TooltipButton(props: AriaButtonProps) { const ref = useRef<HTMLButtonElement>(null); const { buttonProps } = useButton(props, ref); return ( <button {...buttonProps} ref={ref}> {props.children} </button> );
} function TooltipWithTrigger() { const state: TooltipTriggerState = { isOpen: false, open: () => {}, close: () => {}, }; const triggerRef = useRef<HTMLButtonElement>(null); const tooltipRef = useRef<HTMLDivElement>(null); const { triggerProps, tooltipProps } = useTooltipTrigger( { delay: 300 }, state, triggerRef ); const { tooltipProps: positionProps } = useTooltip( tooltipProps, state ); const { overlayProps } = useOverlayPosition({ targetRef: triggerRef, overlayRef: tooltipRef, placement: 'top', offset: 5, isOpen: state.isOpen, }); return ( <div> <button {...triggerProps} ref={triggerRef}> Hover me </button> {state.isOpen && ( <div {...positionProps} {...overlayProps} ref={tooltipRef} > Tooltip content </div> )} </div> );
} COMMAND_BLOCK:
import { useButton } from '@react-aria/button';
import { useTooltip, useTooltipTrigger } from '@react-aria/tooltip';
import { useOverlayPosition } from '@react-aria/overlays';
import { useRef, useState } from 'react';
import type { AriaButtonProps } from '@react-types/button';
import type { TooltipTriggerState } from '@react-stately/tooltip'; function TooltipButton(props: AriaButtonProps) { const ref = useRef<HTMLButtonElement>(null); const { buttonProps } = useButton(props, ref); return ( <button {...buttonProps} ref={ref}> {props.children} </button> );
} function TooltipWithTrigger() { const state: TooltipTriggerState = { isOpen: false, open: () => {}, close: () => {}, }; const triggerRef = useRef<HTMLButtonElement>(null); const tooltipRef = useRef<HTMLDivElement>(null); const { triggerProps, tooltipProps } = useTooltipTrigger( { delay: 300 }, state, triggerRef ); const { tooltipProps: positionProps } = useTooltip( tooltipProps, state ); const { overlayProps } = useOverlayPosition({ targetRef: triggerRef, overlayRef: tooltipRef, placement: 'top', offset: 5, isOpen: state.isOpen, }); return ( <div> <button {...triggerProps} ref={triggerRef}> Hover me </button> {state.isOpen && ( <div {...positionProps} {...overlayProps} ref={tooltipRef} > Tooltip content </div> )} </div> );
} COMMAND_BLOCK:
import { useTable } from 'react-table'; interface Data { id: string; name: string; age: number;
} function Table() { const data: Data[] = [ { id: '1', name: 'John', age: 30 }, { id: '2', name: 'Jane', age: 25 }, ]; const columns = [ { Header: 'Name', accessor: 'name' }, { Header: 'Age', accessor: 'age' }, ]; const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, } = useTable({ columns, data }); return ( <table {...getTableProps()}> <thead> {headerGroups.map((headerGroup) => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map((column) => ( <th {...column.getHeaderProps()}> {column.render('Header')} </th> ))} </tr> ))} </thead> <tbody {...getTableBodyProps()}> {rows.map((row) => { prepareRow(row); return ( <tr {...row.getRowProps()}> {row.cells.map((cell) => ( <td {...cell.getCellProps()}> {cell.render('Cell')} </td> ))} </tr> ); })} </tbody> </table> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useTable } from 'react-table'; interface Data { id: string; name: string; age: number;
} function Table() { const data: Data[] = [ { id: '1', name: 'John', age: 30 }, { id: '2', name: 'Jane', age: 25 }, ]; const columns = [ { Header: 'Name', accessor: 'name' }, { Header: 'Age', accessor: 'age' }, ]; const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, } = useTable({ columns, data }); return ( <table {...getTableProps()}> <thead> {headerGroups.map((headerGroup) => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map((column) => ( <th {...column.getHeaderProps()}> {column.render('Header')} </th> ))} </tr> ))} </thead> <tbody {...getTableBodyProps()}> {rows.map((row) => { prepareRow(row); return ( <tr {...row.getRowProps()}> {row.cells.map((cell) => ( <td {...cell.getCellProps()}> {cell.render('Cell')} </td> ))} </tr> ); })} </tbody> </table> );
} COMMAND_BLOCK:
import { useTable } from 'react-table'; interface Data { id: string; name: string; age: number;
} function Table() { const data: Data[] = [ { id: '1', name: 'John', age: 30 }, { id: '2', name: 'Jane', age: 25 }, ]; const columns = [ { Header: 'Name', accessor: 'name' }, { Header: 'Age', accessor: 'age' }, ]; const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, } = useTable({ columns, data }); return ( <table {...getTableProps()}> <thead> {headerGroups.map((headerGroup) => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map((column) => ( <th {...column.getHeaderProps()}> {column.render('Header')} </th> ))} </tr> ))} </thead> <tbody {...getTableBodyProps()}> {rows.map((row) => { prepareRow(row); return ( <tr {...row.getRowProps()}> {row.cells.map((cell) => ( <td {...cell.getCellProps()}> {cell.render('Cell')} </td> ))} </tr> ); })} </tbody> </table> );
} COMMAND_BLOCK:
export interface UseSelectOptions<T> { items: T[]; onSelect?: (item: T | null) => void; defaultIsOpen?: boolean; defaultHighlightedIndex?: number; itemToString?: (item: T | null) => string;
} export interface SelectState<T> { isOpen: boolean; highlightedIndex: number; selectedItem: T | null; inputValue: string;
} export interface SelectHelpers { openMenu: () => void; closeMenu: () => void; toggleMenu: () => void; selectItem: (item: any) => void; setHighlightedIndex: (index: number) => void; setInputValue: (value: string) => void; reset: () => void;
} export interface SelectReturnValue<T> extends SelectState<T>, SelectHelpers { getToggleButtonProps: () => ToggleButtonProps; getMenuProps: () => MenuProps; getInputProps: () => InputProps; getItemProps: (item: T, index: number) => ItemProps;
} interface ToggleButtonProps { onClick: () => void; 'aria-expanded': boolean; 'aria-haspopup': 'listbox'; 'aria-label': string;
} interface MenuProps { role: 'listbox'; style?: React.CSSProperties;
} interface InputProps { value: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void; onFocus: () => void; role: 'combobox'; 'aria-autocomplete': 'list'; 'aria-expanded': boolean; 'aria-controls': string;
} interface ItemProps { onClick: () => void; onMouseEnter: () => void; role: 'option'; 'aria-selected': boolean; style?: React.CSSProperties;
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
export interface UseSelectOptions<T> { items: T[]; onSelect?: (item: T | null) => void; defaultIsOpen?: boolean; defaultHighlightedIndex?: number; itemToString?: (item: T | null) => string;
} export interface SelectState<T> { isOpen: boolean; highlightedIndex: number; selectedItem: T | null; inputValue: string;
} export interface SelectHelpers { openMenu: () => void; closeMenu: () => void; toggleMenu: () => void; selectItem: (item: any) => void; setHighlightedIndex: (index: number) => void; setInputValue: (value: string) => void; reset: () => void;
} export interface SelectReturnValue<T> extends SelectState<T>, SelectHelpers { getToggleButtonProps: () => ToggleButtonProps; getMenuProps: () => MenuProps; getInputProps: () => InputProps; getItemProps: (item: T, index: number) => ItemProps;
} interface ToggleButtonProps { onClick: () => void; 'aria-expanded': boolean; 'aria-haspopup': 'listbox'; 'aria-label': string;
} interface MenuProps { role: 'listbox'; style?: React.CSSProperties;
} interface InputProps { value: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void; onFocus: () => void; role: 'combobox'; 'aria-autocomplete': 'list'; 'aria-expanded': boolean; 'aria-controls': string;
} interface ItemProps { onClick: () => void; onMouseEnter: () => void; role: 'option'; 'aria-selected': boolean; style?: React.CSSProperties;
} COMMAND_BLOCK:
export interface UseSelectOptions<T> { items: T[]; onSelect?: (item: T | null) => void; defaultIsOpen?: boolean; defaultHighlightedIndex?: number; itemToString?: (item: T | null) => string;
} export interface SelectState<T> { isOpen: boolean; highlightedIndex: number; selectedItem: T | null; inputValue: string;
} export interface SelectHelpers { openMenu: () => void; closeMenu: () => void; toggleMenu: () => void; selectItem: (item: any) => void; setHighlightedIndex: (index: number) => void; setInputValue: (value: string) => void; reset: () => void;
} export interface SelectReturnValue<T> extends SelectState<T>, SelectHelpers { getToggleButtonProps: () => ToggleButtonProps; getMenuProps: () => MenuProps; getInputProps: () => InputProps; getItemProps: (item: T, index: number) => ItemProps;
} interface ToggleButtonProps { onClick: () => void; 'aria-expanded': boolean; 'aria-haspopup': 'listbox'; 'aria-label': string;
} interface MenuProps { role: 'listbox'; style?: React.CSSProperties;
} interface InputProps { value: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void; onFocus: () => void; role: 'combobox'; 'aria-autocomplete': 'list'; 'aria-expanded': boolean; 'aria-controls': string;
} interface ItemProps { onClick: () => void; onMouseEnter: () => void; role: 'option'; 'aria-selected': boolean; style?: React.CSSProperties;
} COMMAND_BLOCK:
import { useState, useRef, useEffect } from 'react';
import type { UseSelectOptions, SelectReturnValue } from './types'; export function useSelect<T>({ items, onSelect, defaultIsOpen = false, defaultHighlightedIndex = 0, itemToString = (item) => (item ? String(item) : ''),
}: UseSelectOptions<T>): SelectReturnValue<T> { const [isOpen, setIsOpen] = useState(defaultIsOpen); const [highlightedIndex, setHighlightedIndex] = useState(defaultHighlightedIndex); const [selectedItem, setSelectedItem] = useState<T | null>(null); const [inputValue, setInputValue] = useState(''); const menuRef = useRef<HTMLElement | null>(null); const buttonRef = useRef<HTMLElement | null>(null); const filteredItems = items.filter((item) => itemToString(item).toLowerCase().includes(inputValue.toLowerCase()) ); useEffect(() => { if (!isOpen) return; const handleClickOutside = (event: MouseEvent) => { if ( menuRef.current && buttonRef.current && !menuRef.current.contains(event.target as Node) && !buttonRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen]); const openMenu = () => { setIsOpen(true); }; const closeMenu = () => { setIsOpen(false); setHighlightedIndex(0); }; const toggleMenu = () => { setIsOpen((prev) => !prev); }; const selectItem = (item: T) => { setSelectedItem(item); setInputValue(itemToString(item)); setIsOpen(false); onSelect?.(item); }; const reset = () => { setSelectedItem(null); setInputValue(''); setHighlightedIndex(0); setIsOpen(false); }; const handleKeyDown = (event: React.KeyboardEvent) => { switch (event.key) { case 'ArrowDown': event.preventDefault(); if (!isOpen) { setIsOpen(true); } else { setHighlightedIndex((prev) => prev < filteredItems.length - 1 ? prev + 1 : prev ); } break; case 'ArrowUp': event.preventDefault(); if (isOpen) { setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : prev)); } break; case 'Enter': event.preventDefault(); if (isOpen && filteredItems[highlightedIndex]) { selectItem(filteredItems[highlightedIndex]); } break; case 'Escape': event.preventDefault(); closeMenu(); break; } }; const getToggleButtonProps = () => ({ onClick: toggleMenu, 'aria-expanded': isOpen, 'aria-haspopup': 'listbox' as const, 'aria-label': 'Toggle menu', }); const getMenuProps = () => ({ role: 'listbox' as const, style: { display: isOpen ? 'block' : 'none', }, }); const getInputProps = () => ({ value: inputValue, onChange: (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); setIsOpen(true); }, onKeyDown: handleKeyDown, onFocus: () => setIsOpen(true), role: 'combobox' as const, 'aria-autocomplete': 'list' as const, 'aria-expanded': isOpen, 'aria-controls': 'select-menu', }); const getItemProps = (item: T, index: number) => ({ onClick: () => selectItem(item), onMouseEnter: () => setHighlightedIndex(index), role: 'option' as const, 'aria-selected': highlightedIndex === index, style: { backgroundColor: highlightedIndex === index ? '#e0e0e0' : 'transparent', }, }); return { // State isOpen, highlightedIndex, selectedItem, inputValue, // Helpers openMenu, closeMenu, toggleMenu, selectItem, setHighlightedIndex, setInputValue, reset, // Prop getters getToggleButtonProps, getMenuProps, getInputProps, getItemProps, };
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useState, useRef, useEffect } from 'react';
import type { UseSelectOptions, SelectReturnValue } from './types'; export function useSelect<T>({ items, onSelect, defaultIsOpen = false, defaultHighlightedIndex = 0, itemToString = (item) => (item ? String(item) : ''),
}: UseSelectOptions<T>): SelectReturnValue<T> { const [isOpen, setIsOpen] = useState(defaultIsOpen); const [highlightedIndex, setHighlightedIndex] = useState(defaultHighlightedIndex); const [selectedItem, setSelectedItem] = useState<T | null>(null); const [inputValue, setInputValue] = useState(''); const menuRef = useRef<HTMLElement | null>(null); const buttonRef = useRef<HTMLElement | null>(null); const filteredItems = items.filter((item) => itemToString(item).toLowerCase().includes(inputValue.toLowerCase()) ); useEffect(() => { if (!isOpen) return; const handleClickOutside = (event: MouseEvent) => { if ( menuRef.current && buttonRef.current && !menuRef.current.contains(event.target as Node) && !buttonRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen]); const openMenu = () => { setIsOpen(true); }; const closeMenu = () => { setIsOpen(false); setHighlightedIndex(0); }; const toggleMenu = () => { setIsOpen((prev) => !prev); }; const selectItem = (item: T) => { setSelectedItem(item); setInputValue(itemToString(item)); setIsOpen(false); onSelect?.(item); }; const reset = () => { setSelectedItem(null); setInputValue(''); setHighlightedIndex(0); setIsOpen(false); }; const handleKeyDown = (event: React.KeyboardEvent) => { switch (event.key) { case 'ArrowDown': event.preventDefault(); if (!isOpen) { setIsOpen(true); } else { setHighlightedIndex((prev) => prev < filteredItems.length - 1 ? prev + 1 : prev ); } break; case 'ArrowUp': event.preventDefault(); if (isOpen) { setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : prev)); } break; case 'Enter': event.preventDefault(); if (isOpen && filteredItems[highlightedIndex]) { selectItem(filteredItems[highlightedIndex]); } break; case 'Escape': event.preventDefault(); closeMenu(); break; } }; const getToggleButtonProps = () => ({ onClick: toggleMenu, 'aria-expanded': isOpen, 'aria-haspopup': 'listbox' as const, 'aria-label': 'Toggle menu', }); const getMenuProps = () => ({ role: 'listbox' as const, style: { display: isOpen ? 'block' : 'none', }, }); const getInputProps = () => ({ value: inputValue, onChange: (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); setIsOpen(true); }, onKeyDown: handleKeyDown, onFocus: () => setIsOpen(true), role: 'combobox' as const, 'aria-autocomplete': 'list' as const, 'aria-expanded': isOpen, 'aria-controls': 'select-menu', }); const getItemProps = (item: T, index: number) => ({ onClick: () => selectItem(item), onMouseEnter: () => setHighlightedIndex(index), role: 'option' as const, 'aria-selected': highlightedIndex === index, style: { backgroundColor: highlightedIndex === index ? '#e0e0e0' : 'transparent', }, }); return { // State isOpen, highlightedIndex, selectedItem, inputValue, // Helpers openMenu, closeMenu, toggleMenu, selectItem, setHighlightedIndex, setInputValue, reset, // Prop getters getToggleButtonProps, getMenuProps, getInputProps, getItemProps, };
} COMMAND_BLOCK:
import { useState, useRef, useEffect } from 'react';
import type { UseSelectOptions, SelectReturnValue } from './types'; export function useSelect<T>({ items, onSelect, defaultIsOpen = false, defaultHighlightedIndex = 0, itemToString = (item) => (item ? String(item) : ''),
}: UseSelectOptions<T>): SelectReturnValue<T> { const [isOpen, setIsOpen] = useState(defaultIsOpen); const [highlightedIndex, setHighlightedIndex] = useState(defaultHighlightedIndex); const [selectedItem, setSelectedItem] = useState<T | null>(null); const [inputValue, setInputValue] = useState(''); const menuRef = useRef<HTMLElement | null>(null); const buttonRef = useRef<HTMLElement | null>(null); const filteredItems = items.filter((item) => itemToString(item).toLowerCase().includes(inputValue.toLowerCase()) ); useEffect(() => { if (!isOpen) return; const handleClickOutside = (event: MouseEvent) => { if ( menuRef.current && buttonRef.current && !menuRef.current.contains(event.target as Node) && !buttonRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen]); const openMenu = () => { setIsOpen(true); }; const closeMenu = () => { setIsOpen(false); setHighlightedIndex(0); }; const toggleMenu = () => { setIsOpen((prev) => !prev); }; const selectItem = (item: T) => { setSelectedItem(item); setInputValue(itemToString(item)); setIsOpen(false); onSelect?.(item); }; const reset = () => { setSelectedItem(null); setInputValue(''); setHighlightedIndex(0); setIsOpen(false); }; const handleKeyDown = (event: React.KeyboardEvent) => { switch (event.key) { case 'ArrowDown': event.preventDefault(); if (!isOpen) { setIsOpen(true); } else { setHighlightedIndex((prev) => prev < filteredItems.length - 1 ? prev + 1 : prev ); } break; case 'ArrowUp': event.preventDefault(); if (isOpen) { setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : prev)); } break; case 'Enter': event.preventDefault(); if (isOpen && filteredItems[highlightedIndex]) { selectItem(filteredItems[highlightedIndex]); } break; case 'Escape': event.preventDefault(); closeMenu(); break; } }; const getToggleButtonProps = () => ({ onClick: toggleMenu, 'aria-expanded': isOpen, 'aria-haspopup': 'listbox' as const, 'aria-label': 'Toggle menu', }); const getMenuProps = () => ({ role: 'listbox' as const, style: { display: isOpen ? 'block' : 'none', }, }); const getInputProps = () => ({ value: inputValue, onChange: (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); setIsOpen(true); }, onKeyDown: handleKeyDown, onFocus: () => setIsOpen(true), role: 'combobox' as const, 'aria-autocomplete': 'list' as const, 'aria-expanded': isOpen, 'aria-controls': 'select-menu', }); const getItemProps = (item: T, index: number) => ({ onClick: () => selectItem(item), onMouseEnter: () => setHighlightedIndex(index), role: 'option' as const, 'aria-selected': highlightedIndex === index, style: { backgroundColor: highlightedIndex === index ? '#e0e0e0' : 'transparent', }, }); return { // State isOpen, highlightedIndex, selectedItem, inputValue, // Helpers openMenu, closeMenu, toggleMenu, selectItem, setHighlightedIndex, setInputValue, reset, // Prop getters getToggleButtonProps, getMenuProps, getInputProps, getItemProps, };
} COMMAND_BLOCK:
import { useSelect } from './useSelect'; interface SimpleSelectProps { items: string[]; onSelect: (item: string) => void;
} export function SimpleSelect({ items, onSelect }: SimpleSelectProps) { const { isOpen, selectedItem, getToggleButtonProps, getMenuProps, getItemProps, } = useSelect({ items, onSelect, }); return ( <div style={{ position: 'relative', width: '200px' }}> <button {...getToggleButtonProps()}> {selectedItem || 'Select an item'} <span style={{ marginLeft: '8px' }}> {isOpen ? '▲' : '▼'} </span> </button> <ul {...getMenuProps()} style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: 0, padding: 0, listStyle: 'none', border: '1px solid #ccc', borderRadius: '4px', backgroundColor: 'white', maxHeight: '200px', overflowY: 'auto', }} > {items.map((item, index) => ( <li key={index} {...getItemProps(item, index)} style={{ padding: '8px 12px', cursor: 'pointer', ...getItemProps(item, index).style, }} > {item} </li> ))} </ul> </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useSelect } from './useSelect'; interface SimpleSelectProps { items: string[]; onSelect: (item: string) => void;
} export function SimpleSelect({ items, onSelect }: SimpleSelectProps) { const { isOpen, selectedItem, getToggleButtonProps, getMenuProps, getItemProps, } = useSelect({ items, onSelect, }); return ( <div style={{ position: 'relative', width: '200px' }}> <button {...getToggleButtonProps()}> {selectedItem || 'Select an item'} <span style={{ marginLeft: '8px' }}> {isOpen ? '▲' : '▼'} </span> </button> <ul {...getMenuProps()} style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: 0, padding: 0, listStyle: 'none', border: '1px solid #ccc', borderRadius: '4px', backgroundColor: 'white', maxHeight: '200px', overflowY: 'auto', }} > {items.map((item, index) => ( <li key={index} {...getItemProps(item, index)} style={{ padding: '8px 12px', cursor: 'pointer', ...getItemProps(item, index).style, }} > {item} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useSelect } from './useSelect'; interface SimpleSelectProps { items: string[]; onSelect: (item: string) => void;
} export function SimpleSelect({ items, onSelect }: SimpleSelectProps) { const { isOpen, selectedItem, getToggleButtonProps, getMenuProps, getItemProps, } = useSelect({ items, onSelect, }); return ( <div style={{ position: 'relative', width: '200px' }}> <button {...getToggleButtonProps()}> {selectedItem || 'Select an item'} <span style={{ marginLeft: '8px' }}> {isOpen ? '▲' : '▼'} </span> </button> <ul {...getMenuProps()} style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: 0, padding: 0, listStyle: 'none', border: '1px solid #ccc', borderRadius: '4px', backgroundColor: 'white', maxHeight: '200px', overflowY: 'auto', }} > {items.map((item, index) => ( <li key={index} {...getItemProps(item, index)} style={{ padding: '8px 12px', cursor: 'pointer', ...getItemProps(item, index).style, }} > {item} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useSelect } from './useSelect'; interface ComboboxSelectProps { items: string[]; onSelect: (item: string) => void;
} export function ComboboxSelect({ items, onSelect }: ComboboxSelectProps) { const { isOpen, inputValue, highlightedIndex, getInputProps, getMenuProps, getItemProps, } = useSelect({ items, onSelect, }); const filteredItems = items.filter((item) => item.toLowerCase().includes(inputValue.toLowerCase()) ); return ( <div style={{ position: 'relative', width: '300px' }}> <input {...getInputProps()} placeholder="Type to search..." style={{ width: '100%', padding: '8px 12px', border: '1px solid #ccc', borderRadius: '4px', }} /> <ul {...getMenuProps()} style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: '4px 0 0 0', padding: 0, listStyle: 'none', border: '1px solid #ccc', borderRadius: '4px', backgroundColor: 'white', maxHeight: '200px', overflowY: 'auto', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', }} > {filteredItems.length === 0 ? ( <li style={{ padding: '8px 12px', color: '#999' }}> No results found </li> ) : ( filteredItems.map((item, index) => ( <li key={index} {...getItemProps(item, index)} style={{ padding: '8px 12px', cursor: 'pointer', ...getItemProps(item, index).style, }} > {item} </li> )) )} </ul> </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useSelect } from './useSelect'; interface ComboboxSelectProps { items: string[]; onSelect: (item: string) => void;
} export function ComboboxSelect({ items, onSelect }: ComboboxSelectProps) { const { isOpen, inputValue, highlightedIndex, getInputProps, getMenuProps, getItemProps, } = useSelect({ items, onSelect, }); const filteredItems = items.filter((item) => item.toLowerCase().includes(inputValue.toLowerCase()) ); return ( <div style={{ position: 'relative', width: '300px' }}> <input {...getInputProps()} placeholder="Type to search..." style={{ width: '100%', padding: '8px 12px', border: '1px solid #ccc', borderRadius: '4px', }} /> <ul {...getMenuProps()} style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: '4px 0 0 0', padding: 0, listStyle: 'none', border: '1px solid #ccc', borderRadius: '4px', backgroundColor: 'white', maxHeight: '200px', overflowY: 'auto', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', }} > {filteredItems.length === 0 ? ( <li style={{ padding: '8px 12px', color: '#999' }}> No results found </li> ) : ( filteredItems.map((item, index) => ( <li key={index} {...getItemProps(item, index)} style={{ padding: '8px 12px', cursor: 'pointer', ...getItemProps(item, index).style, }} > {item} </li> )) )} </ul> </div> );
} COMMAND_BLOCK:
import { useSelect } from './useSelect'; interface ComboboxSelectProps { items: string[]; onSelect: (item: string) => void;
} export function ComboboxSelect({ items, onSelect }: ComboboxSelectProps) { const { isOpen, inputValue, highlightedIndex, getInputProps, getMenuProps, getItemProps, } = useSelect({ items, onSelect, }); const filteredItems = items.filter((item) => item.toLowerCase().includes(inputValue.toLowerCase()) ); return ( <div style={{ position: 'relative', width: '300px' }}> <input {...getInputProps()} placeholder="Type to search..." style={{ width: '100%', padding: '8px 12px', border: '1px solid #ccc', borderRadius: '4px', }} /> <ul {...getMenuProps()} style={{ position: 'absolute', top: '100%', left: 0, right: 0, margin: '4px 0 0 0', padding: 0, listStyle: 'none', border: '1px solid #ccc', borderRadius: '4px', backgroundColor: 'white', maxHeight: '200px', overflowY: 'auto', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', }} > {filteredItems.length === 0 ? ( <li style={{ padding: '8px 12px', color: '#999' }}> No results found </li> ) : ( filteredItems.map((item, index) => ( <li key={index} {...getItemProps(item, index)} style={{ padding: '8px 12px', cursor: 'pointer', ...getItemProps(item, index).style, }} > {item} </li> )) )} </ul> </div> );
} COMMAND_BLOCK:
import { SimpleSelect } from './SimpleSelect';
import { ComboboxSelect } from './ComboboxSelect'; const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape']; function App() { return ( <div style={{ padding: '40px' }}> <h2>Simple Select</h2> <SimpleSelect items={fruits} onSelect={(item) => console.log('Selected:', item)} /> <h2 style={{ marginTop: '40px' }}>Combobox with Search</h2> <ComboboxSelect items={fruits} onSelect={(item) => console.log('Selected:', item)} /> </div> );
} export default App; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { SimpleSelect } from './SimpleSelect';
import { ComboboxSelect } from './ComboboxSelect'; const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape']; function App() { return ( <div style={{ padding: '40px' }}> <h2>Simple Select</h2> <SimpleSelect items={fruits} onSelect={(item) => console.log('Selected:', item)} /> <h2 style={{ marginTop: '40px' }}>Combobox with Search</h2> <ComboboxSelect items={fruits} onSelect={(item) => console.log('Selected:', item)} /> </div> );
} export default App; COMMAND_BLOCK:
import { SimpleSelect } from './SimpleSelect';
import { ComboboxSelect } from './ComboboxSelect'; const fruits = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape']; function App() { return ( <div style={{ padding: '40px' }}> <h2>Simple Select</h2> <SimpleSelect items={fruits} onSelect={(item) => console.log('Selected:', item)} /> <h2 style={{ marginTop: '40px' }}>Combobox with Search</h2> <ComboboxSelect items={fruits} onSelect={(item) => console.log('Selected:', item)} /> </div> );
} export default App; COMMAND_BLOCK:
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/20/solid'; function MyMenu() { return ( <Menu as="div" className="relative"> <MenuButton className="flex items-center gap-2 rounded-lg bg-blue-600 py-2 px-4 text-white"> Options <ChevronDownIcon className="w-4 h-4" /> </MenuButton> <MenuItems anchor="bottom" className="mt-2 w-52 rounded-lg bg-white shadow-lg border border-gray-200" > <MenuItem> {({ focus }) => ( <a href="/account" className={`block px-4 py-2 ${ focus ? 'bg-blue-100 text-blue-900' : 'text-gray-900' }`} > Account settings </a> )} </MenuItem> <MenuItem> {({ focus }) => ( <a href="/support" className={`block px-4 py-2 ${ focus ? 'bg-blue-100 text-blue-900' : 'text-gray-900' }`} > Support </a> )} </MenuItem> <MenuItem disabled> <span className="block px-4 py-2 text-gray-400"> Delete (coming soon) </span> </MenuItem> </MenuItems> </Menu> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/20/solid'; function MyMenu() { return ( <Menu as="div" className="relative"> <MenuButton className="flex items-center gap-2 rounded-lg bg-blue-600 py-2 px-4 text-white"> Options <ChevronDownIcon className="w-4 h-4" /> </MenuButton> <MenuItems anchor="bottom" className="mt-2 w-52 rounded-lg bg-white shadow-lg border border-gray-200" > <MenuItem> {({ focus }) => ( <a href="/account" className={`block px-4 py-2 ${ focus ? 'bg-blue-100 text-blue-900' : 'text-gray-900' }`} > Account settings </a> )} </MenuItem> <MenuItem> {({ focus }) => ( <a href="/support" className={`block px-4 py-2 ${ focus ? 'bg-blue-100 text-blue-900' : 'text-gray-900' }`} > Support </a> )} </MenuItem> <MenuItem disabled> <span className="block px-4 py-2 text-gray-400"> Delete (coming soon) </span> </MenuItem> </MenuItems> </Menu> );
} COMMAND_BLOCK:
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
import { ChevronDownIcon } from '@heroicons/react/20/solid'; function MyMenu() { return ( <Menu as="div" className="relative"> <MenuButton className="flex items-center gap-2 rounded-lg bg-blue-600 py-2 px-4 text-white"> Options <ChevronDownIcon className="w-4 h-4" /> </MenuButton> <MenuItems anchor="bottom" className="mt-2 w-52 rounded-lg bg-white shadow-lg border border-gray-200" > <MenuItem> {({ focus }) => ( <a href="/account" className={`block px-4 py-2 ${ focus ? 'bg-blue-100 text-blue-900' : 'text-gray-900' }`} > Account settings </a> )} </MenuItem> <MenuItem> {({ focus }) => ( <a href="/support" className={`block px-4 py-2 ${ focus ? 'bg-blue-100 text-blue-900' : 'text-gray-900' }`} > Support </a> )} </MenuItem> <MenuItem disabled> <span className="block px-4 py-2 text-gray-400"> Delete (coming soon) </span> </MenuItem> </MenuItems> </Menu> );
} COMMAND_BLOCK:
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, flexRender, createColumnHelper,
} from '@tanstack/react-table';
import { useState } from 'react'; interface Person { id: number; name: string; age: number; email: string;
} const columnHelper = createColumnHelper<Person>(); const columns = [ columnHelper.accessor('name', { header: 'Name', cell: (info) => info.getValue(), }), columnHelper.accessor('age', { header: 'Age', cell: (info) => info.getValue(), }), columnHelper.accessor('email', { header: 'Email', cell: (info) => info.getValue(), }),
]; function DataTable() { const [data] = useState<Person[]>([ { id: 1, name: 'John Doe', age: 30, email: '[email protected]' }, { id: 2, name: 'Jane Smith', age: 25, email: '[email protected]' }, { id: 3, name: 'Bob Johnson', age: 35, email: '[email protected]' }, ]); const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), }); return ( <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} onClick={header.column.getToggleSortingHandler()} className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" > {flexRender( header.column.columnDef.header, header.getContext() )} {{ asc: ' ▲', desc: ' ▼', }[header.column.getIsSorted() as string] ?? null} </th> ))} </tr> ))} </thead> <tbody className="bg-white divide-y divide-gray-200"> {table.getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td key={cell.id} className="px-6 py-4 whitespace-nowrap"> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </table> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, flexRender, createColumnHelper,
} from '@tanstack/react-table';
import { useState } from 'react'; interface Person { id: number; name: string; age: number; email: string;
} const columnHelper = createColumnHelper<Person>(); const columns = [ columnHelper.accessor('name', { header: 'Name', cell: (info) => info.getValue(), }), columnHelper.accessor('age', { header: 'Age', cell: (info) => info.getValue(), }), columnHelper.accessor('email', { header: 'Email', cell: (info) => info.getValue(), }),
]; function DataTable() { const [data] = useState<Person[]>([ { id: 1, name: 'John Doe', age: 30, email: '[email protected]' }, { id: 2, name: 'Jane Smith', age: 25, email: '[email protected]' }, { id: 3, name: 'Bob Johnson', age: 35, email: '[email protected]' }, ]); const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), }); return ( <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} onClick={header.column.getToggleSortingHandler()} className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" > {flexRender( header.column.columnDef.header, header.getContext() )} {{ asc: ' ▲', desc: ' ▼', }[header.column.getIsSorted() as string] ?? null} </th> ))} </tr> ))} </thead> <tbody className="bg-white divide-y divide-gray-200"> {table.getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td key={cell.id} className="px-6 py-4 whitespace-nowrap"> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </table> );
} COMMAND_BLOCK:
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, flexRender, createColumnHelper,
} from '@tanstack/react-table';
import { useState } from 'react'; interface Person { id: number; name: string; age: number; email: string;
} const columnHelper = createColumnHelper<Person>(); const columns = [ columnHelper.accessor('name', { header: 'Name', cell: (info) => info.getValue(), }), columnHelper.accessor('age', { header: 'Age', cell: (info) => info.getValue(), }), columnHelper.accessor('email', { header: 'Email', cell: (info) => info.getValue(), }),
]; function DataTable() { const [data] = useState<Person[]>([ { id: 1, name: 'John Doe', age: 30, email: '[email protected]' }, { id: 2, name: 'Jane Smith', age: 25, email: '[email protected]' }, { id: 3, name: 'Bob Johnson', age: 35, email: '[email protected]' }, ]); const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), }); return ( <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> {table.getHeaderGroups().map((headerGroup) => ( <tr key={headerGroup.id}> {headerGroup.headers.map((header) => ( <th key={header.id} onClick={header.column.getToggleSortingHandler()} className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer" > {flexRender( header.column.columnDef.header, header.getContext() )} {{ asc: ' ▲', desc: ' ▼', }[header.column.getIsSorted() as string] ?? null} </th> ))} </tr> ))} </thead> <tbody className="bg-white divide-y divide-gray-200"> {table.getRowModel().rows.map((row) => ( <tr key={row.id}> {row.getVisibleCells().map((cell) => ( <td key={cell.id} className="px-6 py-4 whitespace-nowrap"> {flexRender(cell.column.columnDef.cell, cell.getContext())} </td> ))} </tr> ))} </tbody> </table> );
} COMMAND_BLOCK:
import { useCombobox } from 'downshift';
import { useState } from 'react'; function DownshiftCombobox() { const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; const [inputItems, setInputItems] = useState(items); const { isOpen, getMenuProps, getInputProps, highlightedIndex, getItemProps, } = useCombobox({ items: inputItems, onInputValueChange: ({ inputValue }) => { setInputItems( items.filter((item) => item.toLowerCase().includes((inputValue ?? '').toLowerCase()) ) ); }, }); return ( <div> <input {...getInputProps()} placeholder="Select a fruit" className="w-full px-4 py-2 border border-gray-300 rounded" /> <ul {...getMenuProps()} className={`absolute mt-1 w-full bg-white shadow-lg max-h-60 rounded-md overflow-auto ${ isOpen ? 'block' : 'hidden' }`} > {isOpen && inputItems.map((item, index) => ( <li key={index} {...getItemProps({ item, index })} className={`px-4 py-2 cursor-pointer ${ highlightedIndex === index ? 'bg-blue-100' : '' }`} > {item} </li> ))} </ul> </div> );
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
import { useCombobox } from 'downshift';
import { useState } from 'react'; function DownshiftCombobox() { const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; const [inputItems, setInputItems] = useState(items); const { isOpen, getMenuProps, getInputProps, highlightedIndex, getItemProps, } = useCombobox({ items: inputItems, onInputValueChange: ({ inputValue }) => { setInputItems( items.filter((item) => item.toLowerCase().includes((inputValue ?? '').toLowerCase()) ) ); }, }); return ( <div> <input {...getInputProps()} placeholder="Select a fruit" className="w-full px-4 py-2 border border-gray-300 rounded" /> <ul {...getMenuProps()} className={`absolute mt-1 w-full bg-white shadow-lg max-h-60 rounded-md overflow-auto ${ isOpen ? 'block' : 'hidden' }`} > {isOpen && inputItems.map((item, index) => ( <li key={index} {...getItemProps({ item, index })} className={`px-4 py-2 cursor-pointer ${ highlightedIndex === index ? 'bg-blue-100' : '' }`} > {item} </li> ))} </ul> </div> );
} COMMAND_BLOCK:
import { useCombobox } from 'downshift';
import { useState } from 'react'; function DownshiftCombobox() { const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']; const [inputItems, setInputItems] = useState(items); const { isOpen, getMenuProps, getInputProps, highlightedIndex, getItemProps, } = useCombobox({ items: inputItems, onInputValueChange: ({ inputValue }) => { setInputItems( items.filter((item) => item.toLowerCase().includes((inputValue ?? '').toLowerCase()) ) ); }, }); return ( <div> <input {...getInputProps()} placeholder="Select a fruit" className="w-full px-4 py-2 border border-gray-300 rounded" /> <ul {...getMenuProps()} className={`absolute mt-1 w-full bg-white shadow-lg max-h-60 rounded-md overflow-auto ${ isOpen ? 'block' : 'hidden' }`} > {isOpen && inputItems.map((item, index) => ( <li key={index} {...getItemProps({ item, index })} className={`px-4 py-2 cursor-pointer ${ highlightedIndex === index ? 'bg-blue-100' : '' }`} > {item} </li> ))} </ul> </div> );
} CODE_BLOCK:
import { Button, Label, ListBox, ListBoxItem, Popover, Select, SelectValue,
} from 'react-aria-components'; function MySelect() { return ( <Select className="w-64"> <Label className="block text-sm font-medium text-gray-700"> Choose a fruit </Label> <Button className="mt-1 w-full flex justify-between items-center px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm"> <SelectValue /> <span aria-hidden="true">▼</span> </Button> <Popover className="w-64 mt-1 bg-white border border-gray-300 rounded-md shadow-lg"> <ListBox className="max-h-60 overflow-auto"> <ListBoxItem id="apple" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Apple </ListBoxItem> <ListBoxItem id="banana" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Banana </ListBoxItem> <ListBoxItem id="cherry" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Cherry </ListBoxItem> </ListBox> </Popover> </Select> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { Button, Label, ListBox, ListBoxItem, Popover, Select, SelectValue,
} from 'react-aria-components'; function MySelect() { return ( <Select className="w-64"> <Label className="block text-sm font-medium text-gray-700"> Choose a fruit </Label> <Button className="mt-1 w-full flex justify-between items-center px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm"> <SelectValue /> <span aria-hidden="true">▼</span> </Button> <Popover className="w-64 mt-1 bg-white border border-gray-300 rounded-md shadow-lg"> <ListBox className="max-h-60 overflow-auto"> <ListBoxItem id="apple" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Apple </ListBoxItem> <ListBoxItem id="banana" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Banana </ListBoxItem> <ListBoxItem id="cherry" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Cherry </ListBoxItem> </ListBox> </Popover> </Select> );
} CODE_BLOCK:
import { Button, Label, ListBox, ListBoxItem, Popover, Select, SelectValue,
} from 'react-aria-components'; function MySelect() { return ( <Select className="w-64"> <Label className="block text-sm font-medium text-gray-700"> Choose a fruit </Label> <Button className="mt-1 w-full flex justify-between items-center px-4 py-2 bg-white border border-gray-300 rounded-md shadow-sm"> <SelectValue /> <span aria-hidden="true">▼</span> </Button> <Popover className="w-64 mt-1 bg-white border border-gray-300 rounded-md shadow-lg"> <ListBox className="max-h-60 overflow-auto"> <ListBoxItem id="apple" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Apple </ListBoxItem> <ListBoxItem id="banana" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Banana </ListBoxItem> <ListBoxItem id="cherry" className="px-4 py-2 cursor-pointer hover:bg-blue-100" > Cherry </ListBoxItem> </ListBox> </Popover> </Select> );
} CODE_BLOCK:
import * as Select from '@radix-ui/react-select';
import { CheckIcon, ChevronDownIcon } from '@radix-ui/react-icons'; function RadixSelect() { return ( <Select.Root> <Select.Trigger className="inline-flex items-center justify-between rounded px-4 py-2 bg-white border border-gray-300 shadow-sm w-64"> <Select.Value placeholder="Select a fruit..." /> <Select.Icon> <ChevronDownIcon /> </Select.Icon> </Select.Trigger> <Select.Portal> <Select.Content className="overflow-hidden bg-white rounded-md shadow-lg border border-gray-200"> <Select.Viewport className="p-1"> <Select.Item value="apple" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Apple</Select.ItemText> </Select.Item> <Select.Item value="banana" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Banana</Select.ItemText> </Select.Item> <Select.Item value="cherry" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Cherry</Select.ItemText> </Select.Item> </Select.Viewport> </Select.Content> </Select.Portal> </Select.Root> );
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import * as Select from '@radix-ui/react-select';
import { CheckIcon, ChevronDownIcon } from '@radix-ui/react-icons'; function RadixSelect() { return ( <Select.Root> <Select.Trigger className="inline-flex items-center justify-between rounded px-4 py-2 bg-white border border-gray-300 shadow-sm w-64"> <Select.Value placeholder="Select a fruit..." /> <Select.Icon> <ChevronDownIcon /> </Select.Icon> </Select.Trigger> <Select.Portal> <Select.Content className="overflow-hidden bg-white rounded-md shadow-lg border border-gray-200"> <Select.Viewport className="p-1"> <Select.Item value="apple" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Apple</Select.ItemText> </Select.Item> <Select.Item value="banana" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Banana</Select.ItemText> </Select.Item> <Select.Item value="cherry" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Cherry</Select.ItemText> </Select.Item> </Select.Viewport> </Select.Content> </Select.Portal> </Select.Root> );
} CODE_BLOCK:
import * as Select from '@radix-ui/react-select';
import { CheckIcon, ChevronDownIcon } from '@radix-ui/react-icons'; function RadixSelect() { return ( <Select.Root> <Select.Trigger className="inline-flex items-center justify-between rounded px-4 py-2 bg-white border border-gray-300 shadow-sm w-64"> <Select.Value placeholder="Select a fruit..." /> <Select.Icon> <ChevronDownIcon /> </Select.Icon> </Select.Trigger> <Select.Portal> <Select.Content className="overflow-hidden bg-white rounded-md shadow-lg border border-gray-200"> <Select.Viewport className="p-1"> <Select.Item value="apple" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Apple</Select.ItemText> </Select.Item> <Select.Item value="banana" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Banana</Select.ItemText> </Select.Item> <Select.Item value="cherry" className="relative flex items-center px-8 py-2 rounded text-sm cursor-pointer hover:bg-blue-100" > <Select.ItemIndicator className="absolute left-2"> <CheckIcon /> </Select.ItemIndicator> <Select.ItemText>Cherry</Select.ItemText> </Select.Item> </Select.Viewport> </Select.Content> </Select.Portal> </Select.Root> );
} - Tienen múltiples partes interconectadas que comparten estado
- Requieren flexibilidad en cómo se organizan sus elementos internos
- Necesitan proporcionar una API expresiva y fácil de entender
- Deben mantener la lógica encapsulada pero permitir personalización en la presentación - Permite máxima flexibilidad en cómo se estructura el markup
- Cada parte del componente puede ser estilizada independientemente
- Mantiene la accesibilidad incorporada sin forzar estructura rígida
- Los desarrolladores tienen control total sobre el orden y anidamiento de elementos - Integración perfecta con Tailwind CSS
- Comportamiento y accesibilidad manejados internamente
- Estilizado completamente bajo tu control
- Transiciones y animaciones fáciles de implementar - Se integra con el sistema de theming
- Props de estilo consistentes en todos los componentes
- Componentes ya vienen con estilos base personalizables
- Responsive design built-in - Permite personalización completa de cada parte del componente
- Mantiene la API consistente entre todos los componentes
- Facilita el override de componentes internos
- Proporciona type safety completo con TypeScript
- Permite pasar props específicas a cada slot sin contaminar el componente principal - Slots se mapean directamente a rutas del sistema de archivos
- Permite renderizado paralelo de múltiples secciones
- Mantiene la separación de concerns a nivel de routing
- Facilita el streaming y loading states independientes
- Type safety automático con TypeScript - Componentes copiables y personalizables
- Integración natural con Tailwind CSS
- Composición clara y predecible
- Fácil de entender y modificar - Props específicas para slots comunes (cover, actions)
- Subcomponentes para secciones complejas (Meta)
- API consistente en toda la librería
- Buena documentación de cada slot disponible - Inyectar props adicionales
- Agregar lógica de ciclo de vida
- Modificar o interceptar props existentes
- Manejar estado
- Conectar con fuentes de datos externas - Inyectar props adicionales
- Agregar lógica de ciclo de vida
- Modificar o interceptar props existentes
- Manejar estado
- Conectar con fuentes de datos externas - Controlado: El estado vive en React (useState, this.state)
- No controlado: El estado vive en el DOM (elemento HTML nativo) - Mejor performance: No causa re-renders en cada tecla
- Menos código boilerplate: No necesitas useState para cada campo
- Menor sobrecarga de memoria: No mantiene estado de React para cada input
- Integración natural con el DOM: Aprovecha el comportamiento nativo del navegador - Validación instantánea: Puede validar en cada cambio
- Estado sincronizado: Todo el estado del formulario está en un solo lugar
- Más fácil de debuggear: El estado es visible en React DevTools
- Reseteo simple: Fácil resetear todos los campos a valores iniciales - Flexibilidad máxima: Los desarrolladores eligen el enfoque que mejor se adapte a sus necesidades
- Sin opiniones fuertes: No fuerza una arquitectura específica
- Casos de uso diversos: Diferentes aplicaciones tienen diferentes requisitos de performance
- Componentes primitivos: Diseñados para ser building blocks, no soluciones completas - Props necesarias para la funcionalidad interna del componente
- Props de accesibilidad (ARIA attributes)
- Event handlers que componen tanto la lógica interna como los handlers personalizados del usuario
- Cualquier prop adicional que el usuario haya pasado - Agregar sus propios event handlers que se componen con los de Downshift
- Personalizar completamente los estilos sin romper la funcionalidad
- Usar cualquier elemento HTML que deseen
- Mantener control total sobre la estructura del DOM - Atributos ARIA completos
- Event handlers para teclado, mouse y touch
- Estados y flags para styling condicional
- Refs cuando son necesarios - Perfecta integración con Tailwind CSS y utility-first workflows
- Manejo completo de accesibilidad (ARIA attributes, keyboard navigation)
- Componentes probados exhaustivamente en navegadores y lectores de pantalla
- Focus management automático y trap de foco en overlays
- Transiciones y animaciones con soporte integrado
- API declarativa que hace el código fácil de leer y mantener - Framework agnostic con adapters para React, Vue, Solid, Svelte
- Extremadamente ligero (10-15kb) con tree-shaking
- Control total sobre markup, estilos y componentes
- Virtualización para manejar millones de filas eficientemente
- Server-side data compatible
- TypeScript first con tipos completos - Pionera del patrón headless en React
- Inspiró a muchas otras librerías headless
- Usada en producción en PayPal y cientos de otras empresas
- Aunque menos popular ahora (debido a hooks modernos), estableció patrones que las librerías modernas aún siguen - Probado exhaustivamente con lectores de pantalla (JAWS, NVDA, VoiceOver, TalkBack)
- Optimizado para mouse, touch y keyboard en todos los dispositivos
- Soporte para 30+ idiomas con RTL incluido
- Focus management sofisticado y automático
- Interacciones micro como long press, drag-to-cancel
- Respaldado por el equipo de accesibilidad de Adobe - Arquitectura modular y composable
- Accesibilidad incorporada siguiendo WAI-ARIA
- TypeScript de primera clase
- Funciona con cualquier framework de estilos
- Base sólida para sistemas de diseño personalizados
how-totutorialguidedev.toaimlservercronroutingrouterswitchnodejavascript