¡Bienvenida! Este es un curso especial de React Hooks

1

¿Qué aprenderás en el Curso Profesional de React Hooks?

2

¿Qué son los React Hooks y cómo cambian el desarrollo con React?

Introducción a React Hooks

3

useState: estado en componentes creados como funciones

4

useEffect: olvida el ciclo de vida, ahora piensa en efectos

5

useContext: la fusión de React Hooks y React Context

6

useReducer: como useState, pero más escalable

7

¿Qué es memoization? Programación funcional en JavaScript

8

useMemo: evita cálculos innecesarios en componentes

9

useRef: manejo profesional de inputs y formularios

10

useCallback: evita cálculos innecesarios en funciones

11

Optimización de componentes en React con React.memo

12

Custom hooks: abstracción en la lógica de tus componentes

13

Third Party Custom Hooks de Redux y React Router

Configura un entorno de desarrollo profesional

14

Proyecto: análisis y retos de Platzi Conf Store

15

Git Hooks con Husky

16

Instalación de Webpack y Babel: presets, plugins y loaders

17

Configuración de Webpack 5 y webpack-dev-server

18

Configuración de Webpack 5 con loaders y estilos

19

Loaders de Webpack para Preprocesadores CSS

20

Flujo de desarrollo seguro y consistente con ESLint y Prettier

Estructura y creación de componentes para Platzi Conf Store

21

Arquitectura de vistas y componentes con React Router DOM

22

Maquetación y estilos del home

23

Maquetación y estilos de la lista de productos

24

Maquetación y estilos del formulario de checkout

25

Maquetación y estilos de la información del usuario

26

Maquetación y estilos del flujo de pago

27

Integración de íconos y conexión con React Router

Integración de React Hooks en Platzi Conf Merch

28

Creando nuestro primer custom hook

29

Implementando useContext en Platzi Conf Merch

30

useContext en la página de checkout

31

useRef en la página de checkout

32

Integrando third party custom hooks en Platzi Conf Merch

Configura mapas y pagos con PayPal y Google Maps

33

Paso a paso para conectar tu aplicación con la API de PayPal

34

Integración de pagos con la API de PayPal

35

Completando la integración de pagos con la API de PayPal

36

Paso a paso para conectar tu aplicación con la API de Google Maps

37

Integración de Google Maps en el mapa de checkout

38

Creando un Custom Hook para Google Maps

Estrategias de deployment profesional

39

Continuous integration y continuous delivery con GitHub Actions

40

Compra del dominio y despliega con Cloudflare

Optimización de aplicaciones web con React

41

Integración de React Helmet para mejorar el SEO con meta etiquetas

42

Análisis de performance con Google Lighthouse

43

Convierte tu aplicación de React en PWA

Bonus: trabaja con Strapi CMS para crear tu propia API

44

Crea una API con Strapi CMS y consúmela con React.js

¿Qué sigue en tu carrera profesional?

45

Próximos pasos para especializarte en frontend

No tienes acceso a esta clase

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

useContext en la página de checkout

30/45
Recursos

Aportes 31

Preguntas 8

Ordenar por:

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

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 …

Array.prototype.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í:

  1. Cambié la función addToCart: reviso si el elemento existe en el carrito. Si no existe lo añado y le agregó un atributo “cant” con valor en 1. Si ya existe aumento el contador del atributo mencionado.
  2. Similar en la funcion removeFromCart, si el contador es 1 elimino el elemento del carrito, de lo contrario reduzco el contador.
  3. En el componente Checkout muestro la cantidad de items por cada producto y reemplacé el botón de eliminar por dos botones para incrementar o decrementar la cantidad de productos.
  4. Actualicé el total de la compra para que tenga en cuenta las cantidades.
// 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

Solucion de Bug al agregar el mismo producto varias veces

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

Instalar uuid

  • Primero instalaremos con npm npm i uuid
  • Posteriormente lo importaremos dentro de nuestro proyecto, para ser especificos en en componente de products, donde tenemos la logica de handleAddToCart import {v4 as uuidv4} from 'uuid';
  • Una ves ya agregamos el elemento a nuestro componente utilizaremos la siguiente logica para agregar un ID unico a cada nuevo producto en el carrito
    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:

  • Por que si le asigno el onClick al icono en vez del boton el evento no se ejecuta debidamente?
  • Se puede asignar el siguiente codigo : onClick={()=>handleRemoveFromCart} para una mejor estetica o afecta en diferente medida a : const handleRemoveFromCart = product =>()=>{}??
  • Respecto a cuando se repiten items, es una mala practica por parte del backen el que no se asigne una propiedad "cantidad"
    Gracias :ha

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