No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Maquetando el ProductDetail

13/31
Recursos

Aportes 49

Preguntas 1

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 鈥渘egar鈥 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>
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 鈥渙nClick鈥:

                <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>
  )
}