Contador de productos en el carrito
Clase 10 de 31 • Curso de React.js con Vite.js y TailwindCSS
Contenido del curso
Clase 10 de 31 • Curso de React.js con Vite.js y TailwindCSS
Contenido del curso
Hernan Dario Caicedo Sanchez
Oscar Meneses Solís
Diego Fernando Caviedes Camaho
Andrés Julian Caro Restrepo
Luis Miguel Aponte Rayo
Miguel Angel Reyes Moreno
Ernesto
Gilbert Ardila
Rubén Ernesto Aragón Gil
Sebastian Rodriguez
David Pacco
Mateo Betancur
Chanel Mariannis Paredes Sánchez
Andres Parra
Andres Parra
Lucas Frazzetta
Bryan Enrique Garay Benavidez
Zoe Valentina Zapotoczny
Edwar Sanchez
Juan carlos Pabon paez
Josue Tapia Linarez
milton coronado
Jose Ever Muñoz Muñoz
Carlos Romero
alexander duque
Abraham Gonzalez
Jose Ever Muñoz Muñoz
Alejandro Mira
Duvan Alexis Palomino Ramirez
Diego Giraldo Ramirez
JOSE FABIAN BONILLA GUZMAN
Podemos usar la desestructuración: permite desempacar valores de arreglos o propiedades de objetos. En este caso desempacamos los valores que trae ShoppingCartContex para una mejor lectura
const { count, setCount } = useContext(ShoppingCartContex) <button onClick={() => setCount(count + 1)} > + </button>
Uffff muchas gracias!!
Epa.!
Les comparto la forma en la que me parece interesante usar el useContext. Sería utilizarlo en el mismo archivo del Context así:
:0, excelente bro, yo si decia que habia algo mas facil para no tener que usar el useContext en cada lugar.
Como les dije antes, estamos haciendo click en un botón... usemos la correspondiente etiqueta button, no un div. Hagamos buenas prácticas:
<button onClick={() => context.setCount(context.Count + 1)} className="absolute top-0 right-0 flex justify-center items-center bg-white w-6 h-6 rounded-full m-2 p-1" > + </button>
En mi navbar estoy haciendo .map a las urls entonces debo verificar si mi url tiene '/cart' para mostrar el contador de los productos:
import { useContext } from "react"; import { NavLink } from "react-router-dom"; import { ShoppingCartContext } from "../../Context"; import { menu1, menu2 } from "../../routes"; export const Navbar = () => { const context = useContext(ShoppingCartContext) const textDecoration = 'underline underline-offset-4' return ( <nav className="flex items-center justify-between w-full py-5 px-8 text-sm fixed z-10 top-0"> <ul className='flex gap-3 items-center'> {menu1.map(link => ( <li key={link.text} className={link.className} > <NavLink to={link.to} className={({isActive})=> isActive ? textDecoration : undefined } > {link.text} </NavLink> </li> ))} </ul> <ul className='flex gap-3 items-center'> {menu2.map(link => ( <li key={link.text} className={link.className} > <NavLink to={link.to} className={({isActive})=> isActive ? textDecoration : undefined } > {link.to === '/cart' ? `${link.text} ${context.Count}` : link.text} </NavLink> </li> ))} </ul> </nav> ) }
si, ya te vimos
mejorar performance del +: algo que podemos hacer para mejorar la experiencia de usuario es poner una acción en el botón del + para que el usuario sepa que está encima de él, en mi caso le agregué esta linea de código para cambiar el color de fondo cuando el mouse entra en el botón y por supuesto cambiar el Div por un Button, html semantico
hover:bg-blue-500
a que se debe que se imprima DOS veces el console.log(), creo que a una biblioteca de JavaScript, que esta relacionada con la instalación o configuración de "hooks", pero quiero saber el por que o si es mejor corregirlo.
Esto se debe a que tenemos el Strict Mode activado. Si abres el archivo main.jsx verás que el componente App está envuelto de la siguiente forma:
<React.StrictMode> <App /> </React.StrictMode>,
El Strict Mode nos ayuda a detectar problemas en nuestro código ejecutando ciertas funciones dos veces únicamente en desarrollo. Para deshabilitarlo solo deber eliminar la etiqueta.
Puedes encontrar más información aquí.
Otra forma de hacerlo es usando un hook personalizado:
import { useContext } from "react"; import { EcommerceContext } from "../context/EcommerceProvider"; const useEcommerce = () => { return useContext(EcommerceContext); }; export { useEcommerce };
Y así no tienen que crear el useContext en cada componente sino que solamente deben llamar al hook y éste ya contiene todas las variables del provider:
import { useEcommerce } from "../hooks/useEcommerce"; const Card = (data) => { const { incrementShoppingCart } = useEcommerce(); return ( <div className="bg-gray-200 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 m-2 px-2 bg-white/70 rounded-full text-black text-xs"> {data?.data?.category} </span> <img className="w-full h-full object-cover rounded-lg" src={data?.data?.image} alt={data?.data?.title} /> <button className="absolute top-0 right-0 m-2 flex justify-center items-center bg-white w-6 h-6 rounded-full hover:bg-black color-black hover:text-white transition-colors duration-200" onClick={incrementShoppingCart} > + </button> </figure> <p className="flex justify-between px-3"> <span className="text-sm font-light truncate">{data?.data?.title}</span> <span className="text-lg font-bold">${data?.data?.price}</span> </p> </div> ); }; export { Card };
Excelente!
Tengo este error, y cno veo que es lo qye tengo mal.
Ya lo solucione!!!
resuta que tenia problemas en la comunicacion en mi <ShopiProvider/> (tenia diferencias en las mayusculas y minusculas). dejare mi error por si alguien le pasa y pueda chequear eso jeje
Lo que podemos hacer también es directamente crear la función de aumentar el contador dentro del Context. Además podemos crear una variable "data" con todo lo que vamos a pasarle al value para que quede más limpio el código.
import { createContext, useState } from 'react'; import PropTypes from 'prop-types'; export const Context = createContext(); export const ContextProvider = ({ children }) => { ContextProvider.propTypes = { children: PropTypes.node.isRequired, }; const [count, setCount] = useState(0); const onAdd = () => { setCount(count + 1); }; const data = { onAdd, count }; return <Context.Provider value={data}>{children}</Context.Provider>; };
Hola comunidad les comparto mis apuntes de la clase, espero les sean de utilidad. Link aquí:
Gracias por el aporte!! <3
Otra forma de hacerlo es con un hook:
Context/index.jsx:
import { createContext, useState } from "react"; export const ShoppingCartContext = createContext(); export const ShoppingCartProvider = ({ children }) => { const [count, setCount] = useState(0); const incrementCount = () => setCount(count + 1); return ( <ShoppingCartContext.Provider value={{ count, incrementCount }}> {children} </ShoppingCartContext.Provider> ) } ```Hooks/useShoppingCart.jsx: ```js import { useContext } from "react"; import { ShoppingCartContext } from "../Context"; export const useShoppingCart = () => { return useContext(ShoppingCartContext); }; ```Pages/App/index.jsx: ```js import { useRoutes, BrowserRouter } from 'react-router-dom' import { ShoppingCartProvider } from '../../Context'; import Home from '../Home'; import MyAccount from '../MyAccount'; import MyOrder from '../MyOrder'; import MyOrders from '../MyOrders'; import NotFound from '../NotFound'; import SingIn from '../SingIn'; import Navbar from '../../Components/Navbar' import './App.css' import Layout from '../../Components/Layout'; const AppRoutes = () => { let routes = useRoutes([ { path: '/', element: <Home /> }, { path: '/my-account', element: <MyAccount /> }, { path: '/my-order', element: <MyOrder /> }, { path: '/my-orders', element: <MyOrders /> }, { path: '/sing-in', element: <SingIn /> }, { path: '/*', element: <NotFound /> }, ]) return routes; } const App = () => { return ( <ShoppingCartProvider> <BrowserRouter> <Navbar /> <Layout> <AppRoutes /> </Layout> </BrowserRouter> </ShoppingCartProvider> ) } export default App ```Components/Card/index.jsx: ```js import { FaPlus } from "react-icons/fa6"; import { useShoppingCart } from "../../Hooks/useShoppingCart"; const Card = (data) => { const { incrementCount } = useShoppingCart(); return ( <article className='bg-white cursor-pointer w-56 h-60 rounded-lg'> <figure className='relative mb-2 w-full h-4/5'> <figcaption 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 || ''}</figcaption> <img className='w-full h-full object-cover rounded-lg' src={data?.data?.images[0]?.replace(/[\[\]"]/g, '') || ''} alt={data?.data?.title} /> <FaPlus className='absolute top-0 right-0 flex justify-center items-center bg-white text-black w-6 h-6 rounded-full m-2 p-1' onClick={incrementCount} /> </figure> <p className='flex justify-between items-center'> <span className='text-sm font-light'>{data?.data?.title}</span> <span className='text-lg font-medium'>${data?.data?.title}</span> </p> </article> ) } export default Card ```Components/Navbar/index.jsx: ```js import { NavLink } from "react-router-dom" import { useShoppingCart } from "../../Hooks/useShoppingCart"; const Navbar = () => { const { count } = useShoppingCart(); const menu1 = [ { to: '/', text: 'Shopi', className: 'font-semibold text-lg' }, { to: '/', text: 'All', className: '' }, { to: '/clothes', text: 'Clothes', className: '' }, { to: '/electronics', text: 'Electronics', className: '' }, { to: '/furnitures', text: 'Furnitures', className: '' }, { to: '/toys', text: 'Toys', className: '' }, { to: '/others', text: 'Others', className: '' } ] const menu2 = [ { to: '/email', text: 'edwar@gmail.com', className: 'text-black/60' }, { to: '/my-orders', text: 'My Orders', className: '' }, { to: '/my-account', text: 'My Account', className: '' }, { to: '/sing-in', text: 'Sing In', className: '' }, { to: '/shoppcar', text: `🛒 ${count}`, className: '' }, ] return ( <nav className="flex justify-between items-center fixed z-10 top-0 w-full py-5 px-8 text-sm font-light"> <ul className='flex items-center gap-3'> {menu1.map(link => ( <li key={link.text} className={link.className}> <NavLink to={link.to} className={({ isActive }) => isActive ? 'underline underline-offset-4' : undefined}> {link.text} </NavLink> </li> ))} </ul> <ul className='flex items-center gap-3'> {menu2.map(link => ( <li key={link.text} className={link.className}> <NavLink to={link.to} className={({ isActive }) => isActive ? 'underline underline-offset-4' : undefined}> {link.text} </NavLink> </li> ))} </ul> </nav> ) } export default Navbar ```Con esto ya podemos importar nuestro hook en los componentes que necesitemos y no necesitamos preocuparnos por los detalles de la implementación del context.
una forma de incrementar en contador sin pasar el estado solo es actualizador del estado es de la siguiente manera:
<button className="absolute top-0 right-0 flex justify-center items-center bg-white w-6 h-6 rounded-full m-1 p-2" value={0} onClick={() => { setcount((value) => value + 1); }} > + </button>
Card.jsx
const { count } = useContext(ShoppingCartContext) <li>Carrito{count}</li>
Context.jsx
const [count, setCount] = useState(0) const contextValue = { count, setCount } return ( <ShoppingCartContext.Provider value={contextValue}> {children} </ShoppingCartContext.Provider> )
en el curso de react router vi que juanDC creava una funcion para recien usarla como constext.AnyThing
Esta es la funcion para llamar ha auth.unaFn || estado
function useAuth() { const auth = React.useContext(AuthContext); return auth; }
luego de la importacion ha un componente se llamaba asi:
const auth = useAuth();
para luego usarla asi:
auth.myFuncionExample()
la profe le pone de nombre context, pero realmente le puedes poner cualquier nombre ya se propsGlobales, estadosGlobales, etc. El hecho es recibas este objeto que contiene los datos que son enviados en la propiead value**** desde el ShoppingCartContext
En el Navbar en vez de estar repitiendo codigo, pueden hacer un array de objetos con los links de la siguiente manera:
const navigationLeft = [ { name: 'All', href: '/' }, { name: 'Clothes', href: '/clothes' }, { name: 'Electronics', href: '/electronics' }, { name: 'Furniture', href: '/furniture' }, { name: 'Toys', href: '/toys' }, { name: 'Others', href: '/others' }, ]; const navigationRight = [ { name: 'My Orders', href: '/my-orders' }, { name: 'My Account', href: '/my-account' }, { name: 'Sign In', href: '/sign-in' }, { name: 'My Order', href: '/my-order' }, ]; ```Luego solo tienen que hacer map por cada uno: ```js {navigationLeft.map(item => { return ( <li key={item.name}> <NavLink to={item.href} className={({ isActive }) => isActive ? activeStyle : 'flex'} > {item.name} </NavLink> </li> ) })} ```En el caso del lado derecho lo que hice para que muestre el carrito en vez del nombre del link fue esto ```js {navigationRight.map(item => { return ( <li key={item.name}> <NavLink to={item.href} className={({ isActive }) => isActive ? activeStyle : 'flex'} > {item.href !== '/my-order' ? item.name : // Si el link es my-order mostrara el carrito <>🛒<span>{count}</span></> } </NavLink> </li> ) })} ```El nombre de la tienda y el correo los hice manualmente: ```js <li className="font-semibold text-lg"> <NavLink to="/"> Shopi </NavLink> </li> // ... <li className="text-gray-400">toxedev@gmail.com</li> ```  
Para el evento del card al agregar un nuevo item al carrito (+) podemos hacer uso del estado previo del state. <div className='absolute top-0 right-0 flex justify-center items-center bg-white w-6 h-6 rounded-full m-2 p-1' onClick={() => setCount(prev => prev + 1)} > + </div>
<div className='absolute top-0 right-0 flex justify-center items-center bg-white w-6 h-6 rounded-full m-2 p-1' onClick={() => setCount(prev => prev + 1)} > + </div>
excelente !
Yo aparte de agregar el contador, agregue un estado nuevo para cambiar el icono de cada card para saber si ya fue agragado y si se le vuelve a dar click lo saca del carrito.
---js const { count, setCount } = useContext(ShoppingCardContext); const [addCart, setAddCart] = useState(false); const addCartItem = () => { if (!addCart) { setCount(count + 1) setAddCart(!addCart) } else { setCount(count - 1) setAddCart(!addCart)
}
}
---
Asi vamos!!!!!
Yo decidí limpiar un poco el archivo de Navbar llevandome toda la info donde se listan en objetos cada uno de los items del Navbar, este sería el resultado:
import { NavLink } from "react-router-dom"; import { useContext } from "react"; import { ShoppingCartContext } from "../../Context"; import { navData } from "../../Assets/nav-data"; const Navbar = () => { const context = useContext(ShoppingCartContext); const navItems = navData(context); const activeStyle = "underline underline-offset-4"; return ( <nav className="flex justify-between items-center fixed top-0 z-10 w-full py-5 px-8 text-sm font-light"> <ul className="flex items-center gap-3"> {navItems.mainNav.map((link) => ( <li className={link.className} key={link.text}> <NavLink to={link.to} className={({isActive}) => isActive ? activeStyle : undefined} > {link.text} </NavLink> </li> ))} </ul> <ul className="flex items-center gap-3"> {navItems.asideMenu.map((link) => ( <li className={link.className} key={link.text}> <NavLink to={link.to} className={({isActive}) => isActive ? activeStyle : undefined} > {link.text} </NavLink> </li> ))} </ul> </nav> ); } export default Navbar;
Y el archivo donde guardo toda la info de los items del Navbar es este:
function navData(prop) { const mainNav = [ { to: '/', text: 'Shopi', className: 'font-semibold text-lg' }, { to: '/', text: 'All', className: '' }, { to: '/clothes', text: 'clothes', className: '' }, { to: '/electronics', text: 'electronics', className: '' }, { to: '/furnitures', text: 'furnitures', className: '' }, { to: '/toys', text: 'toys', className: '' }, { to: '/others', text: 'others', className: '' }, ]; const asideMenu = [ { to: '/email', text: 'juanmer382@gmail.com', className: 'text-black/60' }, { to: '/my-orders', text: 'My Orders', className: '' }, { to: '/my-account', text: 'My Account', className: '' }, { to: '/signin', text: 'Sign In', className: '' }, { to: '/shoppcar', text: `🛒 ${prop.counter}`, className: '' }, ]; return { mainNav, asideMenu } } export {navData};
La carpeta donde hice esto la llamé Assets/nav-data.jsx, no se porque elegí esos nombres pero fue lo primero que se me ocurrio para nombrar estos archivos...
Me gustaría leer sus comentarios para que me ayuden a darle una mejor estructura de carpetas y de nombres para la solución que hice.
muchas gracias en un principio yo queria hacer lo mismo pero no supe como lidiar con el context en mi array mirando el tuyo lo use de guia y lo llame navData.jsx
<import React, { useContext } from 'react' import { StoreContext } from '../../../Context' const navData = () => { const context = useContext(StoreContext) const navItems = [ /* { name: 'Shopi', to: '/', className: 'font-semibold text-xl' }, */ { name: 'All', to: '/' }, { name: 'Electronics', to: '/electronics' }, { name: 'Jewelery', to: '/jewelery' }, { name: "men's clothing", to: '/mens-clothing' }, { name: "women's clothing", to: '/womens-clothing' }, ] const pages = [ { name: "My Orders" ,to: "/my-orders" }, { name: "My Account", to: "/my-account" }, { name: "Sign In", to: "/sign-in" }, { name: `🛒${context.count}`, to: "/cart" }, ]; return { navItems, pages } } export { navData }>
y el index quedo asi
<import React, { useContext } from 'react' import { StoreContext } from '../../../Context' import { navData } from "./navData" import { NavItem } from "./NavItem" import { NavLink } from "react-router-dom" const Navbar = () => { const activeStyle = 'underline underline-offset-4' const data = navData() return ( <nav className='flex justify-between items-center fixed z-10 top-0 w-full py-5 px-8 text-sm font-light'> <ul className='flex items-center gap-3'> <li className="font-semibold text-lg"> <NavLink to="/">Shopi</NavLink> </li> { data.navItems.map(({ to, className, name }) => ( <NavItem key={name} to={to} className={className} navbarName={name} activeStyle={activeStyle} /> )) } </ul> <ul className='flex items-center gap-3'> <li className="text-black/60">Ryo@fake.com</li> { data.pages.map(({ to, className, name }) => ( <NavItem key={name} to={to} className={className} navbarName={name} activeStyle={activeStyle} /> )) } </ul> </nav> ) } >