Andre Huaman Yovera
Carlos Romero
Johann Camilo Belosareos Colmenares
Controlar la visibilidad de un product detail en React se resuelve con un estado booleano centralizado en Context. Aquí aprenderás cómo crear ese estado, exponerlo mediante funciones y conectarlo a las cards para abrir el panel lateral con un clic, usando useContext y clases condicionales de Tailwind.
Por qué centralizar el estado del product detail en Context
Cuando varios componentes necesitan abrir o cerrar el mismo panel, lo más limpio es guardar la lógica en un solo lugar. Context se convierte en la fuente de verdad y evita que cada card duplique funciones.
La idea es simple: una variable booleana decide si el panel se ve o no. Las cards la activan en true y la X del panel la regresa a false.
¿Qué es isProductDetailOpen? Es un estado booleano dentro de Context que indica si el panel lateral está visible. Si vale
true, se muestra; si valefalse, se oculta.
Cómo declarar el estado booleano inicial
Dentro del provider se crea el estado con valor inicial en false para que el panel no aparezca al cargar la app [04:12]. La sintaxis sigue el patrón clásico de useState con un par lectura/modificación.
jsx const [isProductDetailOpen, setIsProductDetailOpen] = useState(false)
El valor por defecto en false garantiza que el usuario solo vea el panel cuando dispare la acción correspondiente.
Cómo crear funciones openProductDetail y closeProductDetail
En lugar de exponer directamente el setter, conviene envolver la lógica en funciones con nombre semántico. Esto deja el código más legible en los componentes que las consumen.
openProductDetail ejecuta setIsProductDetailOpen(true).closeProductDetail ejecuta setIsProductDetailOpen(false).jsx const openProductDetail = () => setIsProductDetailOpen(true) const closeProductDetail = () => setIsProductDetailOpen(false)
Al envolver la lógica, los componentes solo llaman a una función con nombre claro y no se preocupan por cómo se modifica el estado por dentro [05:30].
Qué exponer en el provider del ShoppingCartContext
El provider debe entregar tres piezas a los componentes hijos:
openProductDetail para que las cards abran el panel.closeProductDetail para que la X lo cierre.isProductDetailOpen para que el panel sepa si debe mostrarse.Sin esa tercera pieza, el componente del panel no podría leer su propio estado de visibilidad.
Cómo conectar la card con onClick y useContext
En el componente Card, el contenedor principal recibe un onClick que ejecuta la función traída desde el contexto. Así, hacer clic en cualquier parte de la tarjeta dispara la apertura.
jsx const context = useContext(ShoppingCartContext)
<div onClick={() => context.openProductDetail()}> {/* contenido de la card */} </div>La card no maneja lógica propia: delega todo al contexto. Esto reduce duplicación si más adelante agregas otras tarjetas en distintas vistas [07:45].
¿Por qué llamar a context.openProductDetail en vez de setIsProductDetailOpen? Porque centraliza la intención. El componente expresa qué quiere hacer (abrir el detalle), no cómo se hace internamente.
Cómo mostrar u ocultar el panel con clases dinámicas de Tailwind
Dentro de ProductDetail necesitas inyectar lógica en el atributo className. Para eso, se cambian las comillas simples por backticks y se usa interpolación con ${}.
La decisión es directa: si isProductDetailOpen vale true, se aplica flex; si vale false, se aplica hidden. Tailwind no entiende booleanos, pero sí estas dos clases utilitarias.
jsx const context = useContext(ShoppingCartContext)
<aside className={`${context.isProductDetailOpen ? 'flex' : 'hidden'} flex-col fixed right-0 ...`} > {/* contenido del product detail */} </aside>Este patrón de className condicional es uno de los más usados en proyectos con Tailwind y React, porque mantiene el JSX declarativo sin meter ramas grandes de renderizado [10:20].
Qué error evitar al importar el contexto
Un error común aparece cuando olvidas importar ShoppingCartContext y useContext en el archivo del panel. La consola marca que el contexto no existe y el componente no renderiza.
useContext desde React.ShoppingCartContext desde el archivo donde lo creaste.useContext(ShoppingCartContext) dentro del componente.Con esos tres pasos, el panel lateral ya puede leer el estado y reaccionar al clic de cualquier card.
Reto cómo cerrar el product detail desde la X
El mismo patrón aplica para cerrar el panel. La X dentro de ProductDetail recibe un onClick que ejecuta context.closeProductDetail(), lo que cambia isProductDetailOpen a false y dispara la clase hidden.
Deja en los comentarios cómo te fue con la apertura del product detail y comparte tu solución al reto de cerrarlo desde la X.
Andre Huaman Yovera
Carlos Romero
Johann Camilo Belosareos Colmenares
Wilmer Alexander Mogollon Briceño
Rubén Ernesto Aragón Gil
Santiago Lopera Naranjo
Jose Luis Bedoya
Julián Ignacio Touza del Valle
Angel Rodriguez
Gerson Pineda
Gerson Pineda
david jurado
JOSE FABIAN BONILLA GUZMAN
Rolando Fernández
Luis Alejandro Vesga Osorio
Gilbert Ardila
Uber Cardoso
Juan Manuel Navarro González
Alexander Flores Rayme
Abraham Gonzalez
Carlos Esteban León Pinilla
Paulina Collaguazo
Alexander Flores Rayme
Yerson Garza
Johan Sebastian
Uber Cardoso
Santiago Tamayo Perez
Nelson Londoño
Nelson Londoño
Cristian Acalo
Ali Rodríguez
Ali Rodríguez
Para evitar que se active la función openProductDetail al hacer clic en el PlusIcon dentro del componente Card, puedes detener la propagación del evento llamando al método stopPropagation() en el objeto del evento. Modifica el controlador onClick del PlusIcon de esta manera:
<PlusIcon className="h-6 w-6 text-black" onClick={(e) => { e.stopPropagation(); // Evita que el evento se propague hacia el div principal setCount(count + 1); // Incrementa el contador }} />
De esta manera, cuando hagas clic en el PlusIcon, solo se ejecutará la función setCount, y la función openProductDetail no se activará. El método stopPropagation asegura que el evento de clic no se propague hasta el div principal, que tiene el evento de clic openProductDetail adjunto.
Justamente estaba buscando como lograr esto, muchas gracias!
buen dato!
Así quedaría en nuestro archivo ProductDetail,jsx
<div onClick={() => context.closeProductDetail()}><XMarkIcon className="h-6 w-6 text-black" /></div>
Yeah, así me salió.
Hay una alternativa a la hora de crear la función que se encargará de abrir o cerrar nuestro productDetial, y es realizarlo de la siguiente forma:
const [isProductDetailOpen, setIsProductDetailOpen] = useState(false) const toggleProductDetail = () => setIsProductDetailOpen(!isProductDetailOpen)
¿Que sucede acá? Como verás, al setIsProductDetailOpen le estamos pasando el valor que este mismo useState tiene actualemente, y este es un valor booleano, pero lo estamos negando usando este caracter 👉 !. Este, lo que hace es "negar" el valor actual, esto hace que tome su valor opuesto.
Entonces, si decimos que la varibale a = true, y luego decimos que a = !a, ahora nuestra variable tiene el valor false.
Si quieres comprobarlo, copia y pega este código en la terminal de tu navegador. :)
let a = true console.log(a) // true a = !a console.log(a) //false
#nuncaparesdeaprender
mágico, justo estaba investigando cómo hacer esto! Gracias!
En un principio pensé lo mismo, por que usar 2 funciones, una para abrir y otra para cerrar, si podemos resolverlo con 1 sola? Pero con toggleProductDetail, si estamos viendo el detalle del producto A y hacemos click para ver el detalle del producto B, estamos provocando que se cierre el ProductDetail
A mi lo que me gustaría es que al darle click en el (+) de la card no se despliegue el product detail. No lo he podido hacer aun de forma simple sin que quede muy rebuscado- La solución para el cierre del ProductDetail es usar la funcion que ya teniamos creada en nuestro Context/ index.lsx en nuestro ProductDetail/ index.jsx
<div onClick={context.closeProductDetail}> <XMarkIcon className='h-6 w-6 text-black' /> </div>
Puedes crear una funcion en el componente Card y usar stopPropagation()
<script> const onClick = (event) => { event.stopPropagation(); context.setCount(context.count + 1); } </script>
Luego lo llamas en el button asi
<button onClick={onClick}> </button>
Lo que sucede en nuestro caso se llama bubblin, es una caracteristica de javascript aqui te dejo un enlace para que profundices bubbling usando stopPropagation() evitamos ese comportamiento sien embargo hay que usarlo con cuidado porque puede generar comportamientos inesperados para cierto tipo de funcionalidades como el SEO.
Profe tengo una duda, se podría hacer con el operador AND tipo, el estado isProductDetail && {<acá me renderiza el código>} en vez de usar clases hidden? cual es mas efectiva, y consume menos recursos.
justo venia a comentar lo mismo, lastima este no es curso de juanDC sino el si comenta una respuesta rapida
Son 2 formas de hacerlo, siempre hay que intentar optar por la que sea más legible para tu código según el caso. Yo opte por hacer render condition.
Una alternativa para mostrar el ProductDetail sin tener que manejar las className
<Layout> Home <div className='grid grid-cols-4 gap-5 w-full max-w-screen-lg'> {items?.map((item) => ( <Card key={item.id} data={item} /> ))} </div> {context.isProductDetailOpen && <ProductDetail />} </Layout>
para evitar crear un archivo css s puede poner las propiedades de la siguiente manera
w-[230px] h-1/2vh lg:w-[360px]
de esta manera creamos un width para las vistas pequeñas(moviles y tablets) y otro para las vistas grandes y evitamos el archivo css
por que las imagenes desaparecieron?
Yo hice un estado derivado toggle en vez de un open y otro closed haciendo que cada vez que clickees revierta el valor que isProductDetailOpen tiene en ese momento, es decir, si está en false se vuelve true y si está en true lo pone en false.
const toggleProductDetail = () => setIsProductDetailOpen(!isProductDetailOpen); ```Y el reto simplemente poner en el botón el siguiente onClick: ```js onClick={toggleProductDetail}
Tambien lo pueden har con un solo handleClick:
const handleProductDetailOpen = () => { setIsProductDetailOpen(!isProductDetailOpen); };
const handleProductDetailOpen = () => { setIsProductDetailOpen(!isProductDetailOpen); };
Es una buena solución, pero creo que en nuestro caso no se aplica.
Da clic en una tarjeta y te mostrará los detalles. Pero ahora dale clic en una tarjeta diferente y se cerrará el componente. No te mostrará el detalle de esta otra tarjeta.
Por esto es que ella crea dos funciones
Asi queda el reto de cerrar el side :)
el onClick siempre recibe una función, por lo cual posiblemente te de un error al dejarle solo closeProductDetail.
Seguramente ya lo descubriste, una compañera del futuro (😁) lo dice, pero dejo el comentario, por si alguien lo ve.
Tambien lo pueden har con un solo handleClick:
const handleProductDetailOpen = () => { setIsProductDetailOpen(!isProductDetailOpen); };
mi manera simple de desplegar el productDetail haciendo el llamado onClick en el img en vez del todo el Div
<div className='bg-white cursor-pointer w-56 h-60 rounded-lg'> <figure className='relative mb-2 w-full h-4/5' > <span className='absolute bottom-0 left-0 bg-white/60 rounded-lg text-black text-xs m-2 px-3 py-0.5'>{data.data.category.name}</span> <img className="w-full h-full object-cover rounded-lg " src={data.data.images[0]} alt="product" onClick={() => context.openProductDetail ()} />
y la solución al reto de manera sencilla
<IoMdClose className='text-black h-7 w-7 cursor-pointer' onClick={() => context.closeProductDetail ()}></IoMdClose>
Asi queda el codigo del PlusIcon
<div className='font-medium text-xl' onClick={() => context.closeProductDetails()}> <XMarkIcon className='size-6 text-black' /> </div>
Popr que las imagenes se fueron?
Houston, poseo problemas tengo todo bien pero no veo las vistas de las imágenes.
No te preocupes, al parecer es un problema con la API.
Me paso igual, y busqué errores en el código y no encontré nada, tan solo continué, vi que a la profesora le pasa igual.
Un abrazo.
Así quedo mi onClick para cerrar la ventana.
onClick={() => context.closeProductDetail()} ```*onClick*={() => context.closeProductDetail()}
🦄✨ En lugar de usar la propiedad display para mostrar o no el detalle del producto, usé solamente right. Esto con el fin de que se pueda animar el detalle cuando se muestre y cuando se cierre ;)
<XMarkIcon className = 'h-6 w-6 text-black cursor-pointer' onclick={context.closeProductDetail}/> ```Mi solucion para cerrar ventana de Detail
<XMarkIcon className='h-6 w-6 text-balck cursor-pointer' onClick={context.closeProductDetail}/>