No tienes acceso a esta clase

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

Curso Pr谩ctico de React.js

Curso Pr谩ctico de React.js

Oscar Barajas Tavares

Oscar Barajas Tavares

Eliminando productos del carrito

26/30
Recursos

Aportes 29

Preguntas 6

Ordenar por:

Los aportes, preguntas y respuestas son vitales para aprender en comunidad. Reg铆strate o inicia sesi贸n para participar.

Problemas:
1- Warning sobre la key repetida al agregar al carrito el mismo elemento.
2- Si los 鈥減roductos鈥 agregados son exactamente el mismo, al darle a la X en uno solo, se borran todos los elementos agregados al carrito.

Soluciones:
1- Esto sucede en 鈥/containers/MyOrder.jsx dado que se est谩 usando key={product. id} y pues cuando se agregue al carrito los elementos har谩n referencia al mismo, o sea React dice como, WTF porque se est谩 renderizando el mismo componente 驴?, el Virtual DOM llora, a React le gustan las keys 煤nicas鈥 Pero es f谩cil solucionarlo, dado que, map, tiene como su segundo termino, el index del vector de COMPONENTES <OrderItem /> que se est谩 renderizando. Por lo que solo hay que cambiar en esa parte lo siguiente:

{state.cart.map((product,index) => (
		<OrderItem 
			indexValue={index}
			key={index}
			product={product} 
		/>
))}

Si te fijaste, hay dice tambi茅n indexValue={index}, eso es para solucionar el problema 2.

2-Que se borren todos al querer solo eliminar uno, es porque se estaba utilizando como condicional del filter en useInitialState.js el id del producto, y pues si son iguales, pos todos cumplen con la condici贸n. 馃槄

As铆 que solo hay que llevar ese valor de indexValue a OrderItem.jsx, y NO product.

const OrderItem = (props) => {
	// a mi me gusta asi, pero puedes poner lo que esta 	 
        // dentro de { } ah铆 arriba en vez de props
	const { product, indexValue } = props
	const { removeFromCart } = React.useContext(AppContext)

	const handleRemove = (index) => {
		removeFromCart(index)
	}
	return (
		<div className="OrderItem">
			<figure>
				<img src={product.images[0]} alt={product.title} />
			</figure>
			<p>{product.title}</p>
			<p>{product.price}</p>
			<img 
				src={iconClose} 
				alt="close" 
				onClick={() => handleRemove(indexValue )}
			/>
		</div>
	);

}

export default OrderItem;

Ah铆 le estamos pasando un indexValue unico del vector que se esta creando en addToCart a handleRemove.

Veamos entonces que pasa con ese filter de useInitialState.js:

  const removeFromCart = (indexValue) => {
    setState({
      ...state,
      cart: state.cart.filter((product,index) => index !== indexValue),
    })
  }

Si, ya se, product queda de 鈥渁dorno鈥 pero pues filter funciona as铆, filter(element, index), y nosotros solo necesitamos el index. Ese condicional lo que dice es: 鈥淪ALVA TODOS鈥 los elementos (<OrderItem />) del vector state.cart que NO tengan ese indexValue que llega. El index que llega por supuesto es al que le hacemos click. Por lo tanto, lo saca del vector y el nuevo vector state.cart es sin ese elemento al que hicimos click en la X.

Gr谩ficamente seria (esto es solo ilustrativo, ni es una sintaxis correcta 馃槵):

<MyOrder>

<OrderItem
index=0
product=馃ぁ
removeCart(click( if (index=indexValue)))
/>
<OrderItem
index=1
product=馃ぁ
removeCart(click( if (index=indexValue)))
/>
<OrderItem
index=2
product=馃ぁ
removeCart(click( if (index=indexValue)))
/>

</MyOrder>

Besos 馃槜

Despu茅s de pensar un rato la soluci贸n m谩s simple que encontr茅 fue pasarle tanto el payload como el index, tal que as铆:
useInitialState.js

const removeFromCart = (payload, indexValue) => {
    setState({
      ...state,
      cart: state.cart.filter(
        (item, index) => item.id != payload && index != indexValue
      ),
    });
  }; 

Si eliges el mismo elemento varias veces y despu茅s eliminas 1, se eliminar谩n todos porque todos comparten el mismo id.

Como solucion al problema del id, lo que hice fue que basicamente al agregar un item al carrito creo un nuevo id correspondiente a la longitud del carrito, y al removerlo lo remuevo tomando en cuenta el id nuevo asi:

const addToCart = (payload) =>{
        setState({
            ...state,
            cart : [
                ...state.cart,
                {...payload, idCart:state.cart.length+1},
            ]
        })
    }
    const removeFromCart = (payload) => {
        setState({
            ...state,
            cart: state.cart.filter((product)=>product.idCart!==payload.idCart)
        })
    }```

JAJAJAJAJAJA su reacci贸n cuando dice 鈥渙k鈥 no funcion贸鈥 vale oro

Recomiendo agregar un pointer al icono de borrar del carrito

OrderItem.scss

.OrderItem img {
	cursor: pointer;
}

Si agregamos el mismo elemento, primero, react nos dar谩 el error que OrderItem tiene varios elementos con la misma Key, y si eliminamos uno de estos de estos del carrito tambi茅n se borraran todos.

Para dar una soluci贸n a esto, en product item, yo us茅 Math.random para crear un id unico para item a帽adido al carrito y funciona bien.

const handleCart = (product) =>{
    let idItemInCart =  Math.floor(Math.random() * (500000 - 1000)) 
		addToCart({...product,idItemInCart})
	}

Si bien entre 500.000 y 1000 la probabilidad de que repita es poca para la canitdad de elementos que tenemos, s茅 que esto no es buena practica鈥

Alguien sabe de alguna mejor forma?

Soluci贸n: Bug al eliminar productos repetidos


Para comenzar, el key de cada OrderItem deber铆a estar ligado a su posici贸n en el array de cart para que realmente sea 煤nico. Pero los valores de key sirven a React y no son pasados al componente as铆 que necesitamos otro atributo para pasar el valor del index al componente. Esto lo hacemos as铆:
.
MyOrder.js

<div className="order-products">
	{cart.map( (product, index) => (
            <OrderItem product={product} key={`order-item-${index}`} keyIndex={index} />
          ))}
          
</div>

.
El componente OrderItem recibe el producto y su keyIndex. Pero solo se necesita el keyIndex para disparar la funci贸n removeFromCart.
.
OrderItem.js

function OrderItem ({product, keyIndex}) {

  const { removeFromCart } = useContext(AppContext);

  const handleRemove = () => {
    removeFromCart( keyIndex);
  }

  return (
    <div className="orderItem">
      <figure className="orderItem__img">
        <img src={product.images[0]} className="orderItem__img" alt={product.title} />
      </figure>
      <p className="orderItem__name">
        {product.title}
      </p>
      <p className="orderItem__price">
      ${product.price} 
      </p>
      <img src={iconClose} alt="close" onClick={ handleRemove} />
    </div>

  );
}

.
Finalmente, la funci贸n de removeFromCart funcionar铆a con el m茅todo splice para cortar un elemento de cart en la posici贸n keyIndex. (recuerda que para usar splice se necesita crear una copia del array a modificar)

    const removeFromCart = (keyIndex) => {
        const newCart = state.cart;
        newCart.splice(keyIndex,1);

        setState({
            ...state,
            cart: newCart
        })
    }

Vamos creando la l贸gica para crear una funci贸n que elimine productos del carrito. Recordemos, que la funci贸n para poder a帽adir la ten铆amos en nuestro hook, as铆 que ah铆 mismo vamos a crear una funci贸n para eliminar.

// useInitialState
import { useState } from "react";

const useInitialState = () => {
    const removeFromCart = (payload) => {
        setState({// minificado
            cart: state.cart.filter(item => item.id !== payload.id),
        })
    }

    return {
        state,
        addToCart,
        removeFromCart,
    }
}

export default useInitialState;

Dentro del hook, vamos a crear una funci贸n llamada removeFromCart. Esta funci贸n recibir谩 como par谩metro un payload ( o el producto ), y despu茅s actualizaremos el estado. Para ello, con ayuda de setState, indicamos que ahora el estado seguir谩 siendo un objeto, donde cart, ser谩 el array, PERO usaremos el m茅todo filter, donde eliminaremos el item del array con ayuda de su id, y despu茅s regresar谩 el array sin este elemento. Al final, tenemos que regresar la nueva funci贸n en el hook, es decir, a帽adir return 鈥 , removeFromCart.

// OrderItem.jsx
import React, { useContext } from 'react';
import AppContext from '@context/AppContext';

const OrderItem = ({ product }) => {
	const { removeFromCart } = useContext(AppContext);

	const handleRemove = () => {
		removeFromCart(product);
	}

	return (
		<div className="OrderItem">
			<img src={icon_close} alt="close" onClick={() => handleRemove(product)} />
		</div>
	);
}

export default OrderItem;

Recordemos, que el 铆cono de eliminar, lo tenemos en OrderItem, as铆 que este componente ser谩 el encargado de usar la funci贸n para remover el producto. Para ello, debemos importar tambi茅n useContext para poder trabajar con el contexto. Para implementar la funci贸n, creamos una constante donde traemos la funci贸n removeFromCart del contexto. Despu茅s, creamos una funci贸n que se encargar谩 de manejar el click sobre el 铆cono de eliminar. En esta funci贸n, siempre ejecutaremos la funci贸n removeFromCart con el argumento product, para eventualmente quitarlo de nuestro carrito de compras.

Una manera para manejar el error del id es que pueden agregar una parte que les permita elegir cantidad y ya solo lo multiplican por el costo del producto.

Opinion:
Mencionan muchos que hay un problema a la hora de eliminar los item, pues pueden agregar el mismo y generarse id repetidos. Yo propongo evitar que se pueda agregar item de igual id, pues no tiene sentido agregar 2 veces el mismo elemento, en todo caso deberia asignarse a un mismo elemento una cantidad variable.
Aqui mi codigo para impedir que se agregue elementos de id repetodo

const useInitialState = () =>{
...

    const addToCart = (payload) =>{
        if(!state.cart.includes(payload)){
            setState({
                ...state,
                cart:[...state.cart, payload]
            })
        }
    }
...
}

export default useInitialState;

Para poder agregar solo 1 de por producto y no se repita productos

const addToCart = (payload) => {
        const payloadExist=state.cart.filter((item) => item.id == payload.id);
        if(payloadExist.length==0){
            setState({
                ...state,
               cart: [...state.cart, payload]
            });
        }
    };

Para poder eliminar productos sin ning煤n problema

const removeFromCart = (payload) => {
        setState({
            ...state,
            cart: state.cart.filter(
                (item) => item.id != payload.id),
        });
    };

no use el Id por el error que todos sabemos, entonces use

	const hora = new Date();

para enviar a ProductItem

{products.map((product, index) => (
	<ProductItem product={product} milisegundos={hora.getMilliseconds()} key={index} />
) )}

y recibirlo por props

const ProductItem = ({product,milisegundos}) => {
	const {addTocart} = useContext(AppContext)

	const handleClick = (item,milisegundos) =>{ 
		item.milisegundos = milisegundos
		addTocart(item)
	}
	return (
		<div className="ProductItem">
			<img src={product.images[0]} alt="" />
			<div className="product-info">
				<div>
					<p>$ {product.price}</p>
					<p>{product.title}</p>
				</div>
				<figure onClick={()=> handleClick(product, milisegundos)}>
					<img src={addCart} alt="" />
				</figure>
			</div>
		</div>
	);
}

antes de enviar agregamso los milisegundos a item

const handleClick = (item,milisegundos) =>{ 
	item.milisegundos = milisegundos
	addTocart(item)
}

y removemos asi

  const removeFromCart = (payload ) =>{
        setstate({
            ...state,
            cart: state.cart.filter(items => items.milisegundos !== payload.milisegundos)
        })
    }

siempre pasamos la declaraci贸n de la funci贸n al onClick o cualquier otro evento, nunca una ejecuci贸n de la funci贸n a menos que la ejecuci贸n retorne la declaraci贸n de una funci贸n pero ese ya es otro detalle a considerar

En el bug de los items repetidos, mi solucion fue crearles un 鈥渋d鈥 unico al momento de agregarlos asi no se confundia el componente al momento de identificarlos.

Ac谩 cree un timestamp para detectar en que punto se fueron agregados al cart.

	const timestamp = new Date().getTime();

	const addToCart = (payload) => {
		setState({
			...state,
			cart: [
				...state.cart,
				{
					...payload,
					timestamp
				}
			]
		})
	}

Y ac谩 estoy evaluando el id y el timestamp para para filtrar los elementos del cart asi esten repetidos.

	const removeFromCart = (payload,timestamp) => {
		setState({
			...state,
			cart: state.cart.filter(item => 
				item.id !== payload && item.timestamp !== timestamp)
		})
	}

Al final mi OrderItem queda tal que asi. Espero les pueda servir!

const OrderItem = ({product,id}) => {
  const { removeFromCart } = useContext(AppContext);
  const {title,price,images,timestamp} = product;

  const handleClick = (id,timestamp) => {
    removeFromCart(id,timestamp)
  }

  return (
    <div className="OrderItem">
      <figure>
        <img
          src={images[0]}
          alt={title}
        />
      </figure>
      <p>{title}</p>
      <p>${price}</p>
      <img src={CloseIcon} alt="close" onClick={() => handleClick(id,timestamp)} />
    </div>
  );
};

creando la funci贸n para toggleOrders y no haci茅ndolo directamente en el onClick, ademas a帽adiendo un peque帽o condicional en los dos toggle, se puede abrir una de las dos pesta帽as, Menu o MyOrders, y cerrar a la otra en caso que este abierta, para que no haya interferencia entre las dos pesta帽as

const handleToggle = () => {
		if(toggleOrders) {
			handleToggleOrders();
		}
		setToggle(!toggle);
	}
	const handleToggleOrders = () => {
		if (toggle) {
			handleToggle();
		}
		setToggleOrders(!toggleOrders)		
	}

esto en Header.jsx

Hola amigos les tengo un Hack cuando quieran acceder a sus componentes rapidamente presionen ctrl + colocar el mause sobre su componente y hacer click

una forma sencilla sin modificar mucho el codigo fue en useInitialState modificar el addToCart y removeFromCart de la siguiente manera

  const addToCart = (payload) => {
    setState({
      ...state,
      cart: [...state.cart, { ...payload, idCart: state.cart.length + 1 }]
    });
  };

  const removeFromCart = (payload) => {
    setState({
      ...state,
      cart: state.cart.filter((product) => product.idCart !== payload.idCart),
    });
  }

si bien aparece un alerta que hay aun dos codigo identicos no da ningun problema al momento de agregar y eliminar

Hola Aqu铆 informaci贸n para profundizar en el funcionamiento del m茅todo filter()
https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Tuve problemas, pero hall茅 la soluci贸n, y la comento aqu铆 por si a alguien le sirve.
en la funci贸n removeFromCart en useInitialState es importante considerar que filter por defecto devuelve un array, as铆 que la l贸gica no debe estar en un array. Esta confusi贸n puede venir porque en addToCart s铆 usamos un array en cart.

solucionado hasta aqui, dejo mi repo por si le sirve a alguien鈥

React-Shop

Haciendo el reto de Bughuters que cierra el modal con la flecha del menu del carrito me di cuenta que realmente no entendi nada de React Context en adelante y ahora revisando el error de eliminar un producto duplicado me doy cuenta que ahora si que menos entiendo que esta pasando馃槮

Aqu铆 dejo una posible soluci贸n a los siguientes posibles errores en la aplicaci贸n y adicional a los que usaron el totalPrice como variable global del estado en el useInitialState del video anterior:

  • Se repiten las keys de los elementos al listarse en MyOrder si se selecciona el mismo varias veces
  • Al eliminarse un producto se eliminan todos los que tengan el mismo id
  • El precio no se actualiza, pues no conocemos el elemento que se elimina desde el m茅todo filter()

Primero lo que necesitamos hacer es aprovecharnos del 铆ndice que nos da el m茅todo map() al listar los productos y utilizarlo como key

state.cart.map((product, idx) => {
	//Aqui colocamos al finalizar idx como adicion a la key para que no se repita
	<OrderItem
		idx={idx}
		product={product}
		key={`order-item-${product.id}-${idx}`}
	/>
})

Luego en nuestro OrderItem recibimos el idx como par谩metro y se lo pasamos al removeFromCart()

// Aqui lo recibimos junto al producto
const OrderItem = ({ product, idx }) => {
	const { removeFromCart } = useContext(AppContext);

	return (
		<div className="OrderItem">
			<figure>
				<img src={product.images[0]} alt={product.title} />
			</figure>
			<p>{ product.title }</p>
			<p>{ product.price }</p>
			<!-- Aqui en el handler se lo enviamos a nuestra funcion -->
			<img src={CloseIcon} alt="close" onClick={() => removeFromCart(idx)}/>
		</div>
	);
}

Por 煤ltimo modificamos nuestra funci贸n en el useInitialState()

Nos Apoyamos en la funcion splice
Esta elimina 鈥渪鈥 cantidad de elementos de un array dada una posici贸n inicial y retorna uno nuevo con los elementos eliminados.
En nuestro caso la posici贸n inicial es el idx y solo queremos eliminar un elemento, por eso tomamos el valor [0] del array

const removeFromCart = (idx) => {
	const removedElement = state.cart.splice(idx, 1)[0]

	setState({
		...state,
		cart: state.cart,
		totalPrice: state.totalPrice - removedElement.price
	});
};

Esto se puede mejorar creando un nuevo array de lo que qued贸 despu茅s de haber hecho el splice, pero creo que por ahora es funcional.

Para que el carrito y myOrder bajen mientras hacemos scroll.
en header.scss

.navbar-shopping-cart {
	// position: relative;
	position: fixed;
	right: 10px;
}

en MyOrder.scss

.MyOrder{
	position: fixed;
}

Respecto del problema con tener varios elementos dentro del array con el mismo id, es decir, repetido el producto, se pueden emplear varias cosas.
.
Primero hay qu茅 preguntarse cu谩l es la l贸gica de negocio. 驴Es correcto tener un mismo producto dos o m谩s veces? 馃, si la respuesta es NO entonces no lo deber铆amos permitir.
.
En ese caso, podemos solucionarlo de la siguiente manera:

//En la funci贸n addToCar primero hacemos una validaci贸n, en caso de que se cumpla, enviamos un alert al usuario.
if(carrito.find(item => item.id === producto.id)){
      alert('Ya existe este elemento')
      return
    } 

.
Ahora bien, puede que s铆 sea necesario poder permitir al usuario escoger la cantidad de un producto. Pero para eso no quedar铆a bien usar en una misma lista un producto repetido, sino dar la opci贸n de escoger la cantidad que desea adquirir (en este escenario no gener茅 code porque es un poco m谩s largo)鈥
.
Por otro lado, si queremos dejar el mismo funcionamiento de permitir agregar elementos repetidos, 煤nicamente debemos darles un ID que sea 脷NICO. En este caso podr铆amos crear una funci贸n que nos genere esa id y luego agregarlo al producto al momento de ejecutar addToCar:
.

//Funcion para generar un id UNICO

const generarId = () => {
  const random = Math.random().toString(36).substring(2);
  const fecha  = Date.now().toString(36);
  return random + fecha;
}

//Dificilmente esto se pueda repetir pero puede seguir agregando m谩s cositas

//Agregarle una propiedad al objeto  y que sea nuestro identificador 煤nico
producto.dateAdd = generarId()

.
Una vez agregada la propiedad dateAdd, al momento de filtrar cuando vayamos a eliminar, en vez de hacerlo por la propiedad id del producto, lo hacemos por .dateAdd y con eso podemos seguis trabajando perfectamente.
.
Finalmente, es por esto importante saber cu谩les son nuestros requerimientos, ya que con ello podremos aplicar la l贸gica necesaria para satisfacer correctamente con la aplicaci贸n.

s铆,s铆,s铆,; los errores son nuestros amigos!!!

Yo le a帽ad铆 un color al icono de borrar el producto, cuando pasemos el mouse por encima .

.imgClose:hover{
	filter: invert(59%) sepia(95%) saturate(404%) hue-			 
         rotate(77deg) brightness(94%) contrast(100%);
	cursor: pointer;
	-webkit-filter: invert(59%) sepia(95%) saturate(404%) 							 
        hue-rotate(77deg) brightness(94%) contrast(100%);
} 

este fue el recurso que use para hacer el filtro al ICONO y no tener que hacerlo en Photoshop o algo as铆 馃槃

Recurso: https://codepen.io/sosuke/pen/Pjoqqp

Que tal campeon, buen dia鈥
Le agregue un poco de estilo鈥

Al darle click al carrito, aparece el banner con la 鈥榵鈥 de cerrar.

archivo Header.jsx
{!toggleOrders ? (
            <li
              className="navbar-shopping-cart"
              onClick={showBanner}
            >
              <img src={shoppingCart} alt="shopping cart" />
              {state.cart.length > 0 ? <div>{state.cart.length}</div> : null}
            </li>
          ) : (
            <img id="btn-close" src={close} alt="" onClick={closeBanner} />
          )}

funciones de mostrar y cerrar banner.

 const showBanner = () => {
    setToggleOrders(true);
  };

  const closeBanner = () => {
    setToggleOrders(false);
  };

yo solucione el problema del key repetido usando un numero random al renderizar:

  {state.cart.map((product) => (
          <OrderItem
            product={product}
            key={`orderItem-${Math.floor(Math.random() * product.id)}`}
          />
        ))}