A este punto de la aplicación creo que hay un bug, cuando se añade más elementos del mismo ID (más de una camiseta, por ejemplo) y quiere borrar una camisa (pero no todas), se eliminan todas las camisas al tener el mismo id
¡Bienvenida! Este es un curso especial de React Hooks
¿Qué aprenderás en el Curso Profesional de React Hooks?
¿Qué son los React Hooks y cómo cambian el desarrollo con React?
Introducción a React Hooks
useState: estado en componentes creados como funciones
useEffect: olvida el ciclo de vida, ahora piensa en efectos
useContext: la fusión de React Hooks y React Context
useReducer: como useState, pero más escalable
¿Qué es memoization? Programación funcional en JavaScript
useMemo: evita cálculos innecesarios en componentes
useRef: manejo profesional de inputs y formularios
useCallback: evita cálculos innecesarios en funciones
Optimización de componentes en React con React.memo
Custom hooks: abstracción en la lógica de tus componentes
Third Party Custom Hooks de Redux y React Router
Configura un entorno de desarrollo profesional
Proyecto: análisis y retos de Platzi Conf Store
Git Hooks con Husky
Instalación de Webpack y Babel: presets, plugins y loaders
Configuración de Webpack 5 y webpack-dev-server
Configuración de Webpack 5 con loaders y estilos
Loaders de Webpack para Preprocesadores CSS
Flujo de desarrollo seguro y consistente con ESLint y Prettier
Estructura y creación de componentes para Platzi Conf Store
Arquitectura de vistas y componentes con React Router DOM
Maquetación y estilos del home
Maquetación y estilos de la lista de productos
Maquetación y estilos del formulario de checkout
Maquetación y estilos de la información del usuario
Maquetación y estilos del flujo de pago
Integración de íconos y conexión con React Router
Integración de React Hooks en Platzi Conf Merch
Creando nuestro primer custom hook
Implementando useContext en Platzi Conf Merch
useContext en la página de checkout
useRef en la página de checkout
Integrando third party custom hooks en Platzi Conf Merch
Configura mapas y pagos con PayPal y Google Maps
Paso a paso para conectar tu aplicación con la API de PayPal
Integración de pagos con la API de PayPal
Completando la integración de pagos con la API de PayPal
Paso a paso para conectar tu aplicación con la API de Google Maps
Integración de Google Maps en el mapa de checkout
Creando un Custom Hook para Google Maps
Estrategias de deployment profesional
Continuous integration y continuous delivery con GitHub Actions
Compra del dominio y despliega con Cloudflare
Optimización de aplicaciones web con React
Integración de React Helmet para mejorar el SEO con meta etiquetas
Análisis de performance con Google Lighthouse
Convierte tu aplicación de React en PWA
Bonus: trabaja con Strapi CMS para crear tu propia API
Crea una API con Strapi CMS y consúmela con React.js
¿Qué sigue en tu carrera profesional?
Próximos pasos para especializarte en frontend
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Oscar Barajas Tavares
Aportes 31
Preguntas 8
A este punto de la aplicación creo que hay un bug, cuando se añade más elementos del mismo ID (más de una camiseta, por ejemplo) y quiere borrar una camisa (pero no todas), se eliminan todas las camisas al tener el mismo id
la logica del titulo asi es mas corta:
<h3>{cart.length > 0 ? ‘Orders list’ : ‘No orders’}</h3>
no hace falta repetir tantos h3
¡Hola!, ¿cómo están?
Ya saben que debemos siempre estar pendientes de qué es lo que estamos aprendiendo y de mejorarlo en lo que podamos dándole nuestro toque personal, en este caso ya otros estudiantes expusieron el bug que había al agregar múltiples camisetas en la aplicación y una forma de darle un arreglo rápido, ahora aquí quiero mostrarles algo que me pareció raro que se le pasó a Oscar.
En vez de mapear directamente con los elementos que ya teníamos allí, lo que hice fue crear un nuevo componente llamado CheckoutItem al cual se le pasa el producto más el handleRemove, he aquí como está implementado en el return de Checkout.jsx :
return (
<div className="Checkout">
<div className="Checkout-content">
{cart.length > 0 ? <h3>Lista de pedidos</h3> : <h3>Sin pedidos...</h3>}
{cart.map((item) => (
<CheckoutItem product={item} handleRemove={handleRemove(item)} />
))}
</div>
{cart.length > 0 && (
<div className="Checkout-sidebar">
<h3>Precio Total: ${handleSumTotal()}</h3>
<Link to="/checkout/information">
<button type="button">Continuar pedido</button>
</Link>
</div>
)}
</div>
);
y he aquí como está creado el componente CheckoutItem.jsx:
import React from "react";
import "../styles/components/Checkout.css";
const CheckoutItem = ({ product, handleRemove }) => {
return (
<div className="Checkout-item">
<div className="Checkout-element">
<h4>{product.title}</h4>
<span>${product.price}</span>
</div>
<button type="button" onClick={handleRemove}>
<i className="fas fa-trash-alt" />
</button>
</div>
);
};
export default CheckoutItem;
Nuevamente podemos usar useMemo para memorizar el resultado, esto por si de pronto el componente se renderea sin que cambie carrito
Para solucionar el problema al agregar al carrito un mismo producto realice lo siguiente:
Modifique la funcion de AddtoCart para que agregue una cantidad (qty) a los productos
const addToCart = payload => {
const cartList = state.cart;
let newCartList = cartList;
const index = cartList.findIndex(item => item.id === payload.id);
if (index >= 0) {
newCartList[index] = {
...newCartList[index],
qty: newCartList[index].qty + 1
}
} else {
payload.qty = 1;
newCartList = [...cartList, payload]
}
setState({
...state,
cart: newCartList
});
}
en el header actualice la cantidad total
const reducer = (acumulador, currentValue) => acumulador + currentValue.qty;
const totalQty = cart.reduce(reducer, 0)
<div className='Header-alert'> {totalQty} </div>
en el checkout modifique el precio total del carrito, tambien muestro el precio unitario y por producto
const Checkout = () => {
const { state: { cart }, removeFromCart } = useContext(AppContext);
const handleRemote = product => {
removeFromCart(product)
}
const handleSumtotal = () => {
const reducer = (accumulator, currentValue) => accumulator + (currentValue.price * currentValue.qty);
const sum = cart.reduce(reducer, 0);
return sum;
}
return (
<div className="Checkout">
<div className="Checkout-content">
<h3>Lista de Pedidos:</h3>
{cart.length > 0
? <>
{cart.map(product => (
<div key={product.id} className="Checkout-item">
<div className="Checkout-element">
<h4>{product.title}</h4>
<span>Cant. {product.qty}</span>
<span>$ {product.price}</span>
<span>Total: $ {product.price * product.qty}</span>
</div>
<button
onClick={() => handleRemote(product)}
type="button">
<i className="fas fa-trash-alt" title="Eliminar" />
</button>
</div>
))}
</>
: <p>No hay Articulos en el carrito</p>}
</div>
<div className="Checkout-sidebar">
<h3>Precio Total: {handleSumtotal()}</h3>
<Link to='/checkout/information'>
<button type="button">Continuar pedido</button>
</Link>
</div>
</div>
);
}
finalmente modifique la funcion removeFromCart para que elimine la cantidad y no el producto completo
const removeFromCart = payload => {
const cartList = state.cart;
let newCartList = cartList;
const index = cartList.findIndex(item => item.id === payload.id);
newCartList[index] = {
...newCartList[index],
qty: newCartList[index].qty - 1
}
const updatedList = newCartList.filter(item => item.qty > 0);
setState({
...state,
cart: updatedList,
});
}
Para los que no entiendan la funcion reduce …
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in single output value.
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15
Yo solucioné el problema de las cantidades así:
// src/hooks/useInitialState.js
import React from 'react';
import products from '../initialState';
function useInitialState() {
const [state, setState] = React.useState(products());
function addToCart(payload) {
const element = state.cart.findIndex(item => item.id === payload.id);
let updateCart = [...state.cart];
if (element > -1) {
updateCart[element].cant += 1;
}
else {
updateCart = [...updateCart,{...payload, cant: 1}];
}
setState({...state, cart: updateCart});
}
function removeFromCart(payload){
const element = state.cart.findIndex(item => item.id === payload.id);
let updateCart = [...state.cart];
if(state.cart[element].cant > 1){
updateCart[element].cant -= 1;
}
else{
updateCart= updateCart.filter(item => item.id !== payload.id);
}
setState({...state,cart:updateCart});
}
return {
addToCart,
removeFromCart,
state
}
}
export default useInitialState;
//src/containers/Checkout.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import AppContext from '../context/AppContext';
import '../styles/components/Checkout.css';
function Checkout() {
const {state:{cart}, addToCart ,removeFromCart} = React.useContext(AppContext);
const handleAddToCart = product => () => {
addToCart(product);
}
const handleRemove = product => () => {
removeFromCart(product);
}
const handleSumTotal = () => {
const reducer = (accum, current) => accum + (current.price * current.cant);
return cart.reduce(reducer,0);
}
return (
<div className="Checkout">
<div className="Checkout-content">
{cart.length > 0 ? <h3>Lista de productos:</h3> : <h3>Sin Productos</h3> }
{cart.map((item) => (<div className="Checkout-item" key={item.id}>
<div className="Checkout-element">
<h4>{item.title}</h4>
<p>$<span>{item.price}</span></p>
</div>
<button className="Checkout-item-add" type="button" onClick={handleAddToCart(item)}>+</button>
<span className="Checkout-item-cant">{item.cant}</span>
<button className="Checkout-item-remove" type="button" onClick={handleRemove(item)}>-</button>
</div>))}
</div>
{cart.length > 0 && (<div className="Checkout-sidebar">
<h3>Precio Total: $<span>{handleSumTotal()}</span></h3>
<Link to="/checkout/information">
<button type="button">Continuar pedido</button>
</Link>
</div>)}
</div>
);
}
export default Checkout;
/* src/styles/components/Checkout.css */
.Checkout-item button {
background-color: transparent;
border: solid 1px #a0a0a0;
outline: none;
}
.Checkout-item-add {
border-radius: 5px 0px 0px 5px;
}
.Checkout-item-remove {
border-radius: 0px 5px 5px 0px;
}
.Checkout-item-cant {
width: 20px;
text-align:center;
}
Leyendo los comentarios y analizando la aplicación comprobé que hay un problema cuando agregas mas de 1 mismo producto ya que si ya no quieres 5 camisetas y eliminas 1 pues se borran las 5, además, un 2do problema con este mismo caso es que te aparece un error en la consola indicando que se está repitiendo el key de un elemento. Entonces ayudandome con los aportes de los compañeros lo logré solucionar:
Checkout.jsx
car.map()
useInitialState.js
Les dejo un aporte para que no te borre todos los productos con el mismo id agregando una key a cada prducto del cart:
En useInitialState:
import { useState } from 'react';
import initialState from '../initialState';
const useInitialState = () => {
const [state, setState] = useState(initialState);
const addToCart = (payload) => {
const newProduct = {
key: state.cart.length,
...payload,
};
setState({
...state,
cart: [...state.cart, newProduct],
});
};
const removeFromCart = (payload) => {
setState({
...state,
cart: state.cart.filter((item) => item.key !== payload.key),
});
};
return {
addToCart,
removeFromCart,
state,
};
};
export default useInitialState;
Para arreglar el bug de que se eliminan todos los productos del mismo tipo cuanto intentas eliminar solo uno, cree un contador, y con ese contador elimino los productos, envés de hacerlo con el id del producto
import { useState } from 'react'
import initialState from '../initialState'
const useInitialState = () => {
const [counter, setCounter] = useState(0)
const addCounter = () => {
setCounter(counter + 1)
return counter
}
const [state, setState] = useState(initialState)
const addToCart = payload => {
setState({
...state,
cart: [...state.cart,{...payload,index: addCounter()}]
})
}
const removeFromCart = payload => {
setState({
...state,
cart: state.cart.filter(item => item.index !== payload.index)
})
}
return{
removeFromCart,
addToCart,
state
}
}
export default useInitialState
Si estás en el minuto 5:07, y quieres entender más de la función handleSumTotal**_ sin tener que hacer todo el curso de Redux_**, tal vez te sirva como a mí este artículo corto donde explican lo que es el .reducer en un array, y el useReducer, ya que la definición que vimos en la clase pasada puede ser un poco difícil de aplicar en este caso específico.
Saludos!
Estoy sorprendido, esto es bastante dinámico y entendible
Como muchos ya han notado en la app que estamos desarrollando hay un bug ya que si agregamos por ejemplo 3 Mug, pero nos arrepentimos de comprar 3 y eliminamos 1 veremos que se eliminaran los 3 ya que estamos filtrando por el ID del producto.
Usando uuid para generar Id’s unicos
Este problema podemos resolverlo agregandole un ID unico a cada producto de nuestro array cart, esta es la mandera en la que yo implemente la solucion y espero sea de ayuda
npm i uuid
handleAddToCart
import {v4 as uuidv4} from 'uuid';
const handleAddToCart = (product)=>{ const idCart = uuidv4(); const newProduct = {...product, cartId: idCart}; addToCart(newProduct); }
Espero sea de ayuda esta implementacion para resolver el bug, recuerden que usar el index es una mala practica para identificar un elemento dentro de un array.
Creo que algunos han podido notar el bug de eliminar productos del carrito, eso se puede solucionar si sobreescribimos el id usando alguna librería que genere ids únicos por nosotros, como nanoid:
import { useState } from 'react'
import { nanoid } from 'nanoid'
import initialState from 'initialState'
const useInitialState = () => {
const [state, setState] = useState({
...initialState,
products: initialState.products.map(p => ({ ...p, id: nanoid() }))
})
const addToCart = (product): void => {
setState({
...state,
cart: state.cart.concat({
...product,
id: nanoid()
})
})
}
const removeFromCart = (product): void => {
setState({
...state,
cart: state.cart.filter(p => p.id !== product.id)
})
}
return { state, addToCart, removeFromCart }
}
export default useInitialState
Se que es mejor código entendible, pero si quieren saber la sumatoria de un key específico de un array con objetos, pueden usar esto sin necesidad de escribir todo el código escrito en clase c:, es lo mismo en menos código :
const totalPrice = cart.reduce((a, b) => a + (b.price || 0), 0);
Veo innecesario el template string ya que está en una etiqueta y fácilmente se deja tal cual, solo usar llaves y listo.
<div className="Checkout-sidebar">
<h3>Precio Total $: {handleSumTotal()}</h3>
<Link to="/checkout/information">
<button type="button">Continuar pedido</button>
</Link>
</div>```
Increible, me case con Redux que ni siqueira le habia dado una posibilidad a useContext y este curso esta logrando que cambie de idea…
Que tal comunity?
En typescript tengo un error en el compilaror que es el siguiente: El error se situa en el archivo useInitialstate.tsx en la linea 28.
error TS2548: Type 'ObjectState[] | undefined' is not an array type or does not have a 'Symbol.iterator' method that returns an iterator.
Lo he intendado buscar por el numero de referencia pero nada.
Aqui les dejo mi ultimo commit que es donde se implementa useContext: commit
Tengo las siguientes preguntas:
Hola a todos, me pueden ayudar con un error de Visual Studio Code que no sé realmente como solucionar.
Por cierto, el proyecto compila bien y no da ningún tipo de error en consola. Solo sale ese detalle en el editor.
Resulta que cuando quiero hacer el destructing de los elementos del context me sale este error:
Y si le doy a Quick fix lo único que hace es agregar un línea de código para ignorar el error pero en teoría no lo soluciona sino que agrega un línea de código que no quiero tener en mi proyecto.
Me pueden indicar cómo puedo corregir esto.
Cálculo del costo total en una línea:
const totalPrice = state.cart.reduce((sum, item) => sum + item.price, 0);
Esta fue mi solucion para el bug, no se si sea buena practica o este mal, me pueden decir
const addToCart = (payload) => {
if (State.cart.some((n) => n.id === payload.id)) {
setState({
...State,
cart: [...State.cart, { ...payload, id: Math.random() }],
});
} else {
setState({
...State,
cart: [...State.cart, payload],
});
}
};
Versión optimizada de **handleSumTotal **:
const handleSumTotal = () => {
return cart.reduce((prev, c) => prev + c.price, 0);
}
Agregué en modo oscuro y modo claro, que detecta sí el navegador esta en alguno de esos modos.
body {
background-color: var(--bg-color);
color: var(--text-color);
}
@media (prefers-color-scheme: dark){
:root{
--bg-color: #000;
--text-color: #fff;
}
}
@media (prefers-color-scheme: light){
}
para que les funcione, deben importarlo al archivo App.jsx de routes
👀No olviden agregar una sentencia por si la lista de cart es esta vacia porque sino genera bugs. Asi lo hice yo:
{cart.length <= 0 ? (<h1>No hay nada</h1>) : (
cart.map((product, i) => {
return (
<div key={product.id + i} className="Checkout-item">
<div className="Checkout-element">
<h4>{product.title}</h4>
<span>${product.price}</span>
</div>
<button onClick={() => handleRemove(i)} type="button">
<TrashIcon />
</button>
</div>
);
})
)}
Min 5:05 - 5:20. Es la nueva forma que tienen los profes para decir “Eso lo debieron ver el año pasado, alumnos” 😑
.
Yo pase por ese curso igualmente y aun así no considero que pueda entender del todo aquella funcion.
.
Mas me parece a mi que el profe ya aplica esa funcion por experiencia mas que sepa como explicarla.
.
Aun así cuando hayamos ya curso ese curso que nos recomienda, no esta demás, en mi opinión explicar este tipo de lógica que viene mezclada ya con una sintaxis bastante reducida, que es propia del lenguaje.
.
Agradezco enormemente a los compañeros que tuvieron la misma inquietud y preguntaron, y también a los/as compañeros que supieron explicar de manera mas clara lo que el profe paso por “obvio”.
¡Hola! Estoy siguiendo este curso, pero usando NextJS + TypeScript + Tailwind + Zustand. Aún no lo termino, pero voy agregando poco a poco las cosas que vamos viendo.
Si les interesa ver el proyecto hasta esta clase, les dejo el link del proyecto: https://github.com/d4vsanchez/platzi-conf-merch/tree/create-shopping-cart
Usé Zustand en lugar del contexto simplemente porque me encanta la librería. Tiene una API sencilla, permite tener un estado global sin usar contexto (usa Pub/Sub), usa Hooks como forma principal de consumo, tiene menos boilerplate y es exageradamente fácil de extender a futuro.
Una forma de resolver el bug, cuando se añade más elementos del mismo ID, pero no la e implementado solo se me ocurrio, es cuando se leccionar un producto para enviarlo al carrito deveria agregar un objeto con el producto y la cantidad seleccionada para que en el estado global el cart quede asi
cart: [
{
item:{
'id': '1',
'image': 'https://arepa.s3.amazonaws.com/camiseta.png',
'title': 'Camiseta',
'price': 25,
'description': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
},
ctn:0
},
{
item:{
'id': '1',
'image': 'https://arepa.s3.amazonaws.com/camiseta.png',
'title': 'Camiseta',
'price': 25,
'description': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
},
ctn:0
},
]
ya despues le pueden dar la logica es un poco mas complicado pero creo que podria ser una opcion
Nos estaria faltando pasar la Key de cada item quye estamso generando, adicional le agregue la imagen de cada producto en el listado del checkout:
{cart.map((item) => (
<div key={item.id} className='Checkout-item '>
<div className='Checkout-element'>
<h4>
{item.title}
</h4>
<figure>
<img
className='h-20'
src={item.image}
alt={item.title} />
</figure>
<span>
$
{item.price}
</span>
</div>
< TrashIcon
className='h-6 w-6 text-blue-500 ml-1'
onClick={handleRemove(item)}
/>
</div>
))}
d
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?