No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Maquetando el ProductDetail

13/31
Recursos

Aportes 54

Preguntas 3

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

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.

Así quedaría en nuestro archivo ProductDetail,jsx

<div onClick={() => context.closeProductDetail()}><XMarkIcon className="h-6 w-6 text-black" /></div>

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

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>

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

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>

Tambien lo pueden har con un solo handleClick:

const handleProductDetailOpen = () => {
setIsProductDetailOpen(!isProductDetailOpen);
};

  const handleProductDetailOpen = () => {
    setIsProductDetailOpen(!isProductDetailOpen);
  };

Asi queda el reto de cerrar el side 😃

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>
## 🦄✨ 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 ;) ![](https://static.platzi.com/media/user_upload/image-9552ab37-bfd6-49d0-b36e-cd58de0cbf1f.jpg)
```js <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}/>
Mi solución: ![](https://static.platzi.com/media/user_upload/image-5a3cbfa3-ce8d-48b9-8571-4500d8752c69.jpg)
**RETO DE LA CLASE:** En el componente ProductDetail modificar esta linea de codigo: ```js <XMarkIcon className="size-6 text-white" onClick={() => context.closeProductDetail()} ```
Reto de la Clase - en /ProductDetail/index.jsx : ... \
          \<XMarkIcon             *className*="h-6 w-6 text-black"             *onClick*={() => context.closeProductDetail()} *// Reto Clase Maquetando el Product Detail*          />        \
```js
<XMarkIcon className="h-6 w-6 text-black" onClick={() => context.closeProductDetail()} // Reto Clase Maquetando el Product Detail />
```
En mi caso preferi mandarlo directo ![](https://static.platzi.com/media/user_upload/image-6348feaf-fea2-467c-ac20-c23d6649e8a4.jpg)
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. ```js const toggleProductDetail = () => setIsProductDetailOpen(!isProductDetailOpen); ```Y el reto simplemente poner en el botón el siguiente onClick: ```js onClick={toggleProductDetail} ```
Para que al momento de dar click en el MAS no se abrar el detail dentro de la funcion agregen el stopProppagation adjunto code. ![](https://static.platzi.com/media/user_upload/image-b4a8ef40-be44-4e50-9de3-c5c9e1d83ae0.jpg)
Solución del reto: Agregar la función al onClick en el componente del icono 'close' ```js <XCircleIcon className="h-6 w-6 text-red-900 cursor-pointer" onClick={() => context.closeProductDetail()}> </XCircleIcon> ```
Reto: ![](https://static.platzi.com/media/user_upload/imagen-861b8e7c-749a-466a-979f-3718311ed5c4.jpg)![](https://static.platzi.com/media/user_upload/imagen-28e25f4f-4244-4e4c-b2bd-a167ce630ba4.jpg)
si nosotros creamos un useState con la respectiva función actualizadora del estado (setIsProductDetailOpen)import { createContext, useState } from "react"; const CartContext=createContext() const CartProvider =({children})=>{    const \[count,setCount]=useState(0)    const \[isProductDetailOpen, setIsProductDetailOpen]=useState(false)        return(        \<CartContext.Provider value={{            count,            setCount,            setIsProductDetailOpen,            isProductDetailOpen        }}>            {children}        \</CartContext.Provider>    )} export{CartContext,CartProvider}, no veo la necesidad de crear otras dos funciones para actualizar este estado. por mi parte lo deje de la siguiente manera:```js import { createContext, useState } from "react"; const CartContext=createContext() const CartProvider =({children})=>{ const [count,setCount]=useState(0) const [isProductDetailOpen, setIsProductDetailOpen]=useState(false) return( <CartContext.Provider value={{ count, setCount, setIsProductDetailOpen, isProductDetailOpen }}> {children} </CartContext.Provider> ) } export{CartContext,CartProvider} ```
Podemos unificar ambas funciones en una sola de esta manera. `const toggleProductDetail = () => {setIsProductDetailOpen(!isProductDetailOpen)}`
Una forma de ahorrarnos las funciones de open y close del setIsDetailOpen, es con una funciónm más simple: ```js const switchProductDetail = () => setIsProductDetailOpen((prev) => !prev) ```
yo en mi context creé un toggler para el productDetail ```js const toggleProductDetail = () => setIsProductDetailOpen(!isProductDetailOpen) ```const toggleProductDetail = () => setIsProductDetailOpen(!isProductDetailOpen) Y ese toggler se lo implementé al shoppingBagIcon en el navbar ```js const { counter, toggleProductDetail } = useContext(ShoppingCartContext) ... <ShoppingBagIcon className='h-6 w-6 text-black-500' onClick={() => toggleProductDetail()} />  
{counter}
```const { counter, toggleProductDetail } = useContext(ShoppingCartContext)
en TS: ![](https://static.platzi.com/media/user_upload/carbon%20%283%29-dd83329d-0f20-4f0f-bcf3-8015949ee8cb.jpg)

Se puede enviar los datos del producto directamente al contexto del ProductDetail y con un if en el componente ProductDetail ocultar la información si el State ProductDetail es false y mostrar los detalles cuando el State contiene los datos del producto y el botón para ocultar ProductDetail puede establecer de nuevo en fase el State ProductDetail

// contexto 
import { createContext, useState } from "react";
export const ProductDetailContext = createContext()
export const ProductDetailProvider = ({ children }) => {
    const [productDetails, setProductDetails] = useState(false)

    return (
        <ProductDetailContext.Provider value={{
            productDetails,
            setProductDetails
        }} >
            {children}
        </ProductDetailContext.Provider >)
}

// componente productDetail

import { useContext } from "react";
import { ProductDetailContext } from "../../Context/productDetail"
export const ProductDetail = () => {
    const { productDetails, setProductDetails } = useContext(ProductDetailContext);
    if (productDetails) {
    return (
        <aside className='flex flex-col fixed right-0 border bg-gray-50 dark:bg-zinc-900 border-black rounded-lg w-[360px] h-[calc(100vh-80px)] top-[68px] z-10'>
            <div className='flex justify-between items-center p-6'>
                <h2 className='font-medium text-xl'>Details</h2>
                <button onClick={() => { setProductDetails(false) }}>
                  x
                </button>
            </div>
            <div className='flex flex-col m-5'>
                <h3>{productDetails.title}</h3>
                <img src={productDetails.image} alt={productDetails.title} />
                <p>{productDetails.description}</p></div>
        </aside>
    )
    }
} 
// Componente Card
import { ProductDetailContext } from "../../Context/productDetail"
import { useContext } from "react";

export function Card({ price, title, category, image, id, description }) {
    const { setProductDetails } = useContext(ProductDetailContext);
    return (
        <article onClick={() => { setProductDetails({ price, title, category, image, id, description })
 className=' cursor-pointer w-56 h-60 rounded-lg p-4'
>
 //resto del codigo
        </article>
    )
}

Cada clase del curso del profesor Juan significaban de 10-15min adicionales (y no por qué enseñara mal, más bien porque recién estaba dando mis pininos en React) tratando de solucionar los problemas que saltaban, este curso lo estoy avanzando más rápido lo cuál me da entender que voy por buen camino. yei
![](https://static.platzi.com/media/user_upload/image-bb322f06-e88c-4a1f-bae6-d1898155d5ad.jpg)

Preferi cambiar las clases de right en el condicional de la card para dar un efecto que aparece desde el lado derecho.
.

className={`${isProductDetailOpen ? 'opacity-100 right-2' : 'opacity-0 right-[-380px]'} flex flex-col fixed  top-[80px] p-6 border bg-white/60 backdrop-blur-3xl border-gray-400 rounded-lg w-[360px] h-[calc(100vh-90px)] transition-all duration-[3000]`}
Esta sería la forma en la cual vamos a cerrar el productDetail, pero con el fin de que se haga mas interactivo la X le agregue la clase cursor-pointer \<XMarkIcon                   className='h-6 w-6 text-black cursor-pointer'                  onClick={() => context.closeProductDetail()}                  /> ```js <XMarkIcon className='h-6 w-6 text-black cursor-pointer' onClick={() => context.closeProductDetail()} /> ```

Solucion del Reto:

Para este proyecto estoy utilizando redux en lugar de useContext. Resolví el reto de la siguiente manera: ```js const ProductDetail = () : ReactElement => { const dispatch = useAppDispatch(); return ( <aside className="product-detail flex flex-col fixed border border-black rounded-lg bg-white ">

Detail

<XMarkIcon className="h-6 w-6 text-black-500 cursor-pointer" onClick={() => dispatch(hideProductDetail())}/>
</aside> ) }; hideProductDetail: (state) => { state.isProductDetailOpen = false; }, ```

Acá la solución al reto:
En el div donde pusimos el ícono de cerrar le ponemos el OnClick de closeProductDetail():

<div onClick={() => context.closeProductDetail()}>
	<XMarkIcon className='h-6 w-6 text-black'></XMarkIcon></div>

Y así cada vez que le demos click a la X se cerrará el ProductDetail 😃

        <button onClick={() => context.closeProductDetail()}>
          <XMarkIcon className="w-6 h-6" />
        </button>

const handlerIsOpenDetail = () => {
setIsOpenDetail(!isOpenDetail);
}

Mi solucion

 <svg onClick={() => context.closeProductDetail()} 
          alt="closeButtonProductDetail" 
	  (url del icono)
 </svg>

Me parece mas limpio destructurar y usar if para renderizar o no el componente, así libero algo de lógica del className

import { XMarkIcon } from "@heroicons/react/24/solid";
import { useContext } from "react";
import { ShoppingCardContext } from "../../Context";

const ProductDetail = () => {
  const { isDetailOpen, closeProductDetail } = useContext(ShoppingCardContext);
  if (isDetailOpen)
    return (
      <aside className="w-[360px] flex flex-col fixed right-0 border border-black rounded-lg bg-white h-[calc(100vh-80px)]">
        <div className="flex justify-between items-center p-6">
          <h2 className="font-medium text-xl">Details</h2>
          <div onClick={closeProductDetail}>
            <XMarkIcon className="h-6 w-6 text-black" />
          </div>
        </div>
      </aside>
    );
  else null;
};

export default ProductDetail;

Usualmente me gusta solo pasar variables en los componentes y elementos de JSX mi codigo de ProductDetails quedo asi:

import { XMarkIcon } from '@heroicons/react/24/solid'
import { useContext } from 'react'
import { ShoppingCartContext } from '../../Context'
import './styles.css'

const ProductDetail = () => {
  const context = useContext(ShoppingCartContext)
  const isProductDetailOpen = context.isProductDetailOpen
  const closeProductDetail = () => context.closeProductDetail()
  return (
    <aside 
      className={`${isProductDetailOpen ? 'flex' : 'hidden'} product-detail  flex-col fixed right-0 border border-black rounded bg-white`}
    >
      <div className='flex justify-between items-center p-6'>
        <h2 className='font-medium text-xl'>Detail</h2>
        <div onClick={closeProductDetail}>
          <XMarkIcon className='h-6 w-6 text-black'/>
        </div>
      </div>
    </aside>
  )
}

export default ProductDetail

Reto de la clase

Hola a todos, aquí está mi código del productDetail, lo que hice fue desestructurar la vairable y funcion que vienen desde mi context en este caso, las cuales son isProductDetailOpen y closeProductDetail repectivamente, para así no tener que poner context.isProductDetailOpen por ejemplo. Luego de esto solo hice una condición con la variable isProductDetailOpen, que dice que este bloque de código solo se mostrará cuando isProductDetailOpen sea true. Para cerrar el modal o el detail de los producto solo invoqué la función que habiamos hecho en el context y un onClick en el div donde está el icono de la X. Claro, esta funcion closeProductDetail también llega desde el context como mencioné anteriormente.

Para mi esta forma en la que tengo el código es un poco más entendible.

import { useContext } from "react";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { ShoppingCartContext } from "../../context";

const ProductDetail = () => {
  const { isProductDetailOpen, closeProductDetail } =
    useContext(ShoppingCartContext);
    
  return (
    <>
      {isProductDetailOpen && (
        <aside className=" w-full flex flex-col fixed right-0 border border-black rounded-lg bg-white md:w[260px] lg:w-[360px] lg:h-[calc(100vh-80px)] lg:top-[58px]">
          <div className="flex justify-between items-center p-6">
            <h2 className="font-medium text-xl">Detail</h2>
            <div onClick={() => closeProductDetail()}>
              <XMarkIcon className="h-6 w-6 text-black" />
            </div>
          </div>
        </aside>
      )}
    </>
  );
};

export default ProductDetail;

Oyeeee, interesante, no habia pensando hacerlo de esta forma. Validando el valor de isProductDetailOpen para setear la clase como flex o hidden, yo lo habia hecho con un renderizado condicional de toda la vida
.

  {context.isProductDetailOpen && <ProductDetail />}

.
Esto de alguna forma lo hace un poco más sencillo de usar porque dentro del componente en si ya tenemos esa logica y no hay que ensuciar el codigo (por decirlo de alguna forma) haciendo la condicional en otros componentes

reto cumplido:

Para que no se abra nuestro aside al agregar el producto al carrito (al darle en el +) podemos utilizar el stopPropagation de la siguiente manera:

 
Aquí dejo para el copy paste jejeje:

onClick={(event) => {event.stopPropagation(); incrementProductCounter();}}>

Así va mi Product Detail

Si das clic en el botón de agregar al carrito (+), también te va a abrir el Product Detail porque ese botón está dentro del elemento div donde se está agregando el evento openProductDetail. Esto es lo que se conoce como propagación de eventos y aunque no se recomienda detener la propagación, en estos casos sí podemos hacerlo porque no necesitamos, al menos por ahora, desencadenar otras acciones a consecuencia de este clic. Solo tienes que agregar evento.stopPropagation(). En mi caso, cree una función por aparte y luego llamé a esa función dentro del onClick del span. No agregué la lógica directamente en el onClick .

Adjunto mi solución:

import { useContext } from "react";
import { ShoppingCartContext } from "../../Context";
import { XMarkIcon } from "@heroicons/react/24/solid";
import "./styles.css";

const ProductDetail = () => {
  const { isProductDetailOpen, closeProductDetail } =
    useContext(ShoppingCartContext);

  return (
    <aside className={`${isProductDetailOpen ? 'flex' : 'hidden'} product-detail flex-col fixed right-10 border border-black rounded-lg bg-white`}>
      <div className="flex justify-between items-center p-6">
        <h2 className="font-medium text-xl">Detail</h2>
        <div>
          <XMarkIcon
            onClick={() => closeProductDetail()} 
            className="h-6 w-6 text-black"></XMarkIcon>
        </div>
      </div>
    </aside>
  );
};

export default ProductDetail;

Reto:

<code> 
 <div onClick={() => context.closeProductDetail()}>
          <XMarkIcon className="h-6 w-6 text-black cur"></XMarkIcon>
        </div>

Agregré al incono lo siguiente en el evento “onClick”:

                <XMarkIcon 
                    className={`h-6 w-6 text-black-500 cursor-pointer bg-gray-100 rounded-full p-1`} 
                    onClick={() => { context?.closeProductDetail()}}
                />
context.closeProductDetail()}> <XMarkIcon className="h-6 w-6 text-gray-500" />
import { useContext } from 'react'
import { XMarkIcon } from '@heroicons/react/24/outline'
import { ShoppingCardContext } from '../../Context'

const ProductDetail = () => {
  const context = useContext(ShoppingCardContext)
  return (
    <aside className={`${context.isProductDetailOpen ? 'block' : 'hidden'} w-[360px] h-[92vh] bg-white fixed right-0 top-[68px] border border-black rounded-lg`}>
      <div className="flex justify-between items-center p-3">
        <h2 className='font-medium text-lg'>Detail</h2>
        <span onClick={() => context.closeProductDetail()}><XMarkIcon className='w-6 h-6 text-black' /></span>
      </div>
    </aside>
  )
}

export default ProductDetail

<div>
<XMarkIcon onClick={()=> context.closeProductDetail()} className='w-6 h-6 text-black cursor-pointer '/>
</div>

Reto conseguido:

import { useContext } from 'react'
import { ShoppingCartContext } from '../../Context'

import './styles.css'

export const ProductDetail = () => {
  const context = useContext(ShoppingCartContext)

  return (
    <aside
      className={`${context.IsProductDetailOpen ? 'flex' : 'hidden'} product-detail flex-col fixed right-0 border border-black rounded-lg bg-white`}
    >
      <div className='flex justify-between items-center p-6'>
        <h2 className='font-medium text-xl'>Detail</h2>
        <button onClick={() => context.closeProductDetail()}>
          {/* X close icon */}
          <svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6">
            <path fillRule="evenodd" d="M5.47 5.47a.75.75 0 011.06 0L12 10.94l5.47-5.47a.75.75 0 111.06 1.06L13.06 12l5.47 5.47a.75.75 0 11-1.06 1.06L12 13.06l-5.47 5.47a.75.75 0 01-1.06-1.06L10.94 12 5.47 6.53a.75.75 0 010-1.06z" clipRule="evenodd" />
          </svg>
        </button>
      </div>
    </aside>
  )
}

function Detail() {
  const { openFunc, closeDetail } = useContext(ShoppingCartContext);

  return (
    <aside 
    className={` ${openFunc ? 'flex' : 'hidden'} w-[360px] h-[calc(100vh-68px)] flex-col fixed top-[68px] right-0 border border-black rounded-lg bg-white p-4`}>
        <div className='flex justify-between items-center'>
            <h2 className='font-medium text-xl'>Detail</h2>
            <div className='text-2xl cursor-pointer' onClick={() => closeDetail()}>
              <AiFillCloseCircle />
            </div>
        </div>
    </aside>
  )
}