Corrigiendo bugs de la aplicación
Clase 29 de 31 • Curso de React.js con Vite.js y TailwindCSS
Contenido del curso
Clase 29 de 31 • Curso de React.js con Vite.js y TailwindCSS
Contenido del curso
Andres Eduardo Maneiro Antunez
Daniel Alberto Cifuentes Castro
david jurado
Annderson Rey Sánchez
Gianluca Enzo Procopio
Abraham Gonzalez
Hugo Roberto Gutiérrez Valentín
Carlos Florez
Xavier Flores
david jurado
David Esteban Giraldo Duque
Gustavo Eduardo Navarro Rodríguez
david jurado
Rubén Vega
Cristian Acalo
Martín Tamagno
Jesus David Bravo Vergara
Diego Matías Segovia
Gustavo Eduardo Navarro Rodríguez
Henry Alejandro Taby Zenteno
Jorge Gaitan
Martin Mendez
Humberto Cruz
Kengya Moncada
Gustavo Eduardo Navarro Rodríguez
Willie David Roa Hidalgo
Invitado Certificado
Milton Estrada
Esto no lo han explicado mucho pero al useEffect se le puede enviar un return que sirve para limpiar los estados cuando el componente se desmonta por ejemplo cuando cambiamos de la ruta del Home a la ruta de MyOrders, se desmonta Home, para monta MyOrders
el useEffect tiene la siguiente estructura
useEffect(() => { //codigo que va a ejecutar first //funcion que sirve para limpiar cuando el componente es desmontado return () => { second } //las dependencias del useEffect }, [third])
en el caso de la clase y con el codigo de la profesora el useEffect seria
useEffect(() => { if (searchByTitle && searchByCategory) setFilteredItems(filterBy('BY_TITLE_AND_CATEGORY', items, searchByTitle, searchByCategory)) if (searchByTitle && !searchByCategory) setFilteredItems(filterBy('BY_TITLE', items, searchByTitle, searchByCategory)) if (!searchByTitle && searchByCategory) setFilteredItems(filterBy('BY_CATEGORY', items, searchByTitle, searchByCategory)) if (!searchByTitle && !searchByCategory) setFilteredItems(filterBy(null, items, searchByTitle, searchByCategory)) return () => { setSearchByTitle(null) } }, [items, searchByTitle, searchByCategory])
asi se aseguran que el input se limpie al cambiar de ruta, la profe solo lo uso al hace checkout, pero que pasa si entras directamente a myOrders o my account desde la navegacion, el input se mantendra, aunque eso pertenece a la logica de negocio, a lo mejor si se quiere que funcione asi el codigo, igual espero que les sirva este aporte
Mi viejo, usted se merece una cerveza! Justo estaba buscando como hacer eso
Que buen aporte! eso es muy importante, siempre que se hace un fetch se debe de retornar para desmontar el componente, o al menos eso vi yo por ahí. aquí dejo mi aporte por si le sirve de ayuda alguien, yo lo hice con next, pero igual sirve sin next
En lo personal siento que se tomo el camino más largo para filtrar por categorías, esto se soluciona con rutas dinámicas
{ path: '/:category', element: <Home /> }
Capturamos el valor de la categoría de esta forma como lo hicimos para ver las órdenes.
const currentPath = window.location.pathname let index = currentPath.substring(currentPath.lastIndexOf('/') + 1); if(index){ return( filteredItems?.filter(item => item.category === index).map((item)=>( <Card key={item.id} {...item}/>)) ); } else { return( filteredItems?.map((item) => <Card key={item.id} {...item} />)); }
Con esto es suficiente para hacer el filtro y adicional a ello si tenemos X cantidad de Categorías va seguir funcionando
Es justo lo que yo hice, creé un custom hook:
import React, {useContext} from 'react'; import { Context } from '../../Context'; import Loader from '../Loader'; import Layout from '../Layout'; import Card from '../Card'; function UseCertainCategory({cat}) { const {products, loading} = useContext(Context) const category = products?.filter(product => product.category.name == cat); return ( <Layout> {!loading && <Loader/>} <div className='grid place-content-center lg:grid-cols-4 gap-3 mt-5 w-full max-w-screen-lg md:grid-cols-3 sm:grid-cols-2 grid-cols-1'> {loading && category?.map(product=>( <Card key={product.id} name={product.title} category={product.category.name} img={product.images[0]} price={product.price} description={product.description} id={product.id}/> )) } </div> </Layout> ) } export default UseCertainCategory;
Y en el archivo de app:
{ path: "/clothes", element: <UseCertainCategory cat={"Clothes"} /> }, { path: "/electronics", element: <UseCertainCategory cat={"Electronics"} /> }, { path: "/furniture", element: <UseCertainCategory cat={"Furniture"} /> }, { path: "/shoes", element: <UseCertainCategory cat={"Shoes"} /> }, { path: "/others", element: <UseCertainCategory cat={"Others"} /> }
Concuerdo contigo, es más sencillo el uso de rutas dinámicas
{ path: '/category/:category', element: <Home /> }
Tan solo agregaria que podemos usar el hook useParams() para capturar el valor del path. No hace falta el uso del window.location.pathname
Podemos usarlo de la siguiente forma
import { useParams } from "react-router-dom"; const {category} = useParams();
Incluso si deseas validar su existencia (que exista category) puedes hacer uso del useEffect
Otro bug.
Filtrar por categorías tiene el mismo porblema del Filtrar por título,cuando has seleccionado una y quieres volver a All o todos,no se borra el valor setSearchBy Category.
Así quedó mi mi versión del proyecto.
me gusto el curso, el código aun tiene mucho por mejorar, pero ya es cuestión nuestra.
El único bug que sigo viendo es que si recargas la página en alguna ruta, puede ser, /clothes, /electronics, no va a tomar la categoria porque la categoria la toma con el onClick del nav, esa partecita me gustaria que ayudaran a resolverlaxd -Yo pensé en una solución que es que cuando se recargue la página siempre inicie en /, la raiz -otra creo que seria que category se guarde en el local storage, pero depronto da más problemas, si alguien sabe porfa ayudaxd
Yo lo solucioné agregando la siguiente ruta:
{ path: "/:category", element: <Home /> }
Además, en el componente Home utilicé el hook useParams para obtener la categoría:
const {category} = useParams()
Efectivamente existe este bug, yo lo solucioné haciendo al ruta :category de forma dinámica y utilizando el hook useParams()
En las rutas:
{ path: '/:category', element: <Home /> },
En el Home
const { category } = useParams() useEffect(() => { if (category?.length > 0) context.setSearchByCategory(category.toLowerCase()) }, [category])
De esta forma no tenemos que depender de ese onClick
una solución rapidita y sencilla es poner un value en donde está el input, en mi caso uso searchTerm, pero si ponemos en value como el ejemplo de la profe, searchByTitle, sirve de la misma manera. siempre nos saldrá el valor y podemos borrarlo.
Puede parecer una tonteria pero, en pantallas mas pequeñas, el menu superior tiene posicion fixed y al hacer scroll no es legible y se entremezcla con los productos del e-commerce. Añadiendo un simple background white, queda perfecto. . Gracias por el curso, he aprendido mucho y refrescado conceptos
🦄✨Me gustó bastante como finalizó el proyecto. Con una api que tenga más datos como el número de productos disponibles, proveedor, etc, sería un gran proyecto para portafolio.
También podríamos agregar el reseteo del count, para que vacíe el carrito una vez que hacemos el checkout:
context.setCount(0)
A alguien le pasa que no se muestran las imagenes de todos los productos?
Holaa, tengo una consulta. Arreglamos el bug de limpiar el valor del input desde el checkout, pero cuando vamos simplemente pasando por las categorías desde el home, al querer volver al listado de todos los productos sin filtrado, no funciona. Cómo se puede solucionar eso?
Tienes que mandar la función sin la categoria en el onClick, por ejemplo:
En el NavBar
<NavLink to='' onClick={() => context.setSearchByCategory()} > All </NavLink>
En MyOrder (index.jsx), me mostraba el siguiente error en el navegador:
Caused by: TypeError: can't access property "products", context.order[index] is undefinedimport { useContext } from 'react'import { Link } from 'react-router-dom'import { ShoppingCartContext } from '../../Context'import Layout from '../../Components/Layout'import OrderCard from '../../Components/OrderCard'import { ChevronLeftIcon } from '@heroicons/react/24/solid' function MyOrder() { const context = useContext(ShoppingCartContext) const currentPath = window.location.pathname let** index** = currentPath.substring(currentPath.lastIndexOf('/')** +** 1) // Validar si el índice es 'last' y calcular el índice correcto if(index === 'last') index = context.order?.length -1 const renderView = ()=>** {** index** = Number(index);** if** (isNaN(index)** ||** index** <** 0** ||** index** >= context.order?.length)** { return(** <p className='text-center text-red-500'>Order not found.</p>** )** } ** const order = context.order[index];** if** (!order?.products || order.products.length ===** 0)** { return (** <p className='text-center text-red-500'>No products in this order.</p>** )** } return ( context.order?.[index].products.map(product =>** (** <OrderCard key={*product.*id} id={*product.*id} title={*product.*title} imageUrl={product.image} price={product.price} /> )) ) ** } return ( <Layout> <div className='flex items-center justify-center w-120 relative mb-6'> <Link to='/my-orders' className='absolute left-0'> <ChevronLeftIcon className='h-6 w-6 text-blue-500 x-mark cursor-pointer' /> </Link> <h2>My Order</h2> </div> <div className='flex flex-col w-120'> {renderView()} </div> </Layout> )} export default MyOrder
Salia despues de hacer el Checkout, y mostraba la lista de productos comprados e inmediatamente se hacia click en cualquier categoria del Navbar.
Este es el código que me aumente y me funciono.
import { useContext } from 'react' import { Link } from 'react-router-dom' import { ShoppingCartContext } from '../../Context' import Layout from '../../Components/Layout' import OrderCard from '../../Components/OrderCard' import { ChevronLeftIcon } from '@heroicons/react/24/solid' function MyOrder() { const context = useContext(ShoppingCartContext) const currentPath = window.location.pathname let index = currentPath.substring(currentPath.lastIndexOf('/') + 1) // Validar si el índice es 'last' y calcular el índice correcto if(index === 'last') index = context.order?.length -1 const renderView = ()=> { index = Number(index); if (isNaN(index) || index < 0 || index >= context.order?.length) { return( <p className='text-center text-red-500'>Order not found.</p> ) } const order = context.order[index]; if (!order?.products || order.products.length === 0) { return ( <p className='text-center text-red-500'>No products in this order.</p> ) } return ( context.order?.[index].products.map(product => ( <OrderCard key={product.id} id={product.id} title={product.title} imageUrl={product.image} price={product.price} /> )) ) } return ( <Layout> <div className='flex items-center justify-center w-120 relative mb-6'> <Link to='/my-orders' className='absolute left-0'> <ChevronLeftIcon className='h-6 w-6 text-blue-500 x-mark cursor-pointer' /> </Link> <h2>My Order</h2> </div> <div className='flex flex-col w-120'> {renderView()} </div> </Layout> ) } export default MyOrder
Yo dejo por aquí lo que hice.
En el home hice:
useEffect(() => { context.setSearchByCategory(category?.length > 0 ? category.toLowerCase() : "all") }, [category]) ```Y en el contexto hice: ```js const filteredItemsByCategory = (items, searchByCategory) => { if(searchByCategory === "all"){ return items; } return items.filter(item => item.category?.name?.toLowerCase().includes(searchByCategory.toLowerCase())) } useEffect(() => { let _items = items; if(searchByCategory) _items = filteredItemsByCategory(_items, searchByCategory) if(searchByTitle) _items = filteredItemsByTitle(_items, searchByTitle) setFilteredItems(_items) }, [items, searchByTitle, searchByCategory]) ```Saludos.
Hola faltó meterle un blocker al boton de Checkout del Side menu. siempre está creando una nueva orden aunque no tenga productos adentro.const renderCheckoutButton = () => { if(context.cartProducts.length > 0) { return ( <Link to='/orders/last'> <button className="w-full bg-gray-900 py-3 text-violet-300 rounded-lg" onClick={() => handleCheckout()}> Checkout </button> </Link> ) } else { return ( <button className="w-full bg-gray-400 py-3 text-violet-200 rounded-lg"> Checkout </button> ) } }
Le mandé un render() y quedó algo asi ..
const renderCheckoutButton = () => { if(context.cartProducts.length > 0) { return ( <Link to='/orders/last'> <button className="w-full bg-gray-900 py-3 text-violet-300 rounded-lg" onClick={() => handleCheckout()}> Checkout </button> </Link> ) } else { return ( <button className="w-full bg-gray-400 py-3 text-violet-200 rounded-lg"> Checkout </button> ) } }
hola, en mi caso solucione dos cosas.
null cada vez que la ruta cambie, esto lo hice añadiendo en el Home una constante location = useLocation(); que captura la ruta en la cual nos encontramos, y después añadí un useEffect(() => context.searchByTitle(null), [location]); .AppRoutes las rutas de las clases anteriores por {path: /home/categoryProduct, element: <home />} y en Home agregar const { categoryProduct } = useParams(); edemas de un useEffect que cambie cada vez que cambie categoryProduct de la siguiente manera:
~~~
useEffect(() => { if (categoryProduct?.length > 0) { context.setCurrentCategory(categoryProduct.toLocaleLowerCase()); } }, [categoryProduct]); ~~~acá dejo el código de los componentes de Home y App con los cambios que hice.
Home:
import { useContext, useEffect } from "react"; import { useLocation, useParams } from "react-router-dom"; import { ShoppingCartContext } from "../../context"; import Layout from "../../components/Layuot"; import Card from "../../components/Card"; import ProductDetail from "../../components/ProductDetail"; import CheckoutSideMenu from "../../components/CheckoutSideMenu"; import { FaceFrownIcon } from "@heroicons/react/24/outline"; function Home() { const location = useLocation(); const context = useContext(ShoppingCartContext); const { categoryProduct } = useParams(); const renderView = () => { const itemsToRender = context.searchByTitle?.length > 0 || context.currentCategory?.length > 0 ? context.filteredItems : context.items; if (itemsToRender?.length > 0) { return itemsToRender.map((item) => <Card key={item.id} data={item} />); } else { return ( <div className="m-10 p-4 h-44 col-span-5 flex flex-col items-center justify-center bg-amber-50 rounded-lg"> <FaceFrownIcon className="m-2 size-20" /> <h3>{"Sorry, we can't find your product"}</h3> </div> ); } }; useEffect(() => { if (categoryProduct?.length > 0) { context.setCurrentCategory(categoryProduct.toLocaleLowerCase()); } }, [categoryProduct]); useEffect(() => { context.setSearchByTitle(null); }, [location]); return ( <Layout> <div className="m-2 p-1 "> <h1 className="text-center text-lg font-medium">Our Products</h2> <input type="text" placeholder="find your products" className="mb-2 p-2 w-72 text-center text-sm border border-black rounded-lg focus:outline-none focus:shadow-lg" onChange={(e) => context.setSearchByTitle(e.target.value)} /> </div> <div className="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 xl:max-w-screen-xl justify-items-center w-full max-w-screen-lg"> {renderView()} </div> <ProductDetail /> <CheckoutSideMenu /> </Layout> ); } export default Home;
App:
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 SignIn from "../SignIn"; import Navbar from "../../components/Navbar"; import "./App.css"; const AppRoutes = () => { let routes = useRoutes([ { path: "/", element: <Home />, }, { path: "/home/:categoryProduct", element: <Home />, }, { path: "/my-account", element: <MyAccount />, }, { path: "/my-order", element: <MyOrder />, }, { path: "/my-orders", element: <MyOrders />, }, { path: "/my-orders/last", element: <MyOrder />, }, { path: "/my-orders/:id", element: <MyOrder />, }, { path: "/sign-in", element: <SignIn />, }, { path: "/*", element: <NotFound />, }, ]); return routes; }; function App() { return ( <ShoppingCartProvider> <BrowserRouter> <AppRoutes /> <Navbar /> </BrowserRouter> </ShoppingCartProvider> ); } export default App; ```Espero que mi aporte les sirva 😊.
me ha costado el ruteo
Para hacer la ruta de forma dinamica podemos usar el hook useParams, de la siguiente forma:
En las rutas:
{ path: '/:category', element: <Home /> },
En el Home
const { category } = useParams() useEffect(() => { if (category?.length > 0) context.setSearchByCategory(category.toLowerCase()) }, [category])
De esta forma no tenemos que depender de ese onClick y tenemos acceso a las categorias por medio de la url
Aun no resuelvo eso 😭 y lo he intentado de muchas maneras, hasta con chatGPT y todo. 😭 ¿Me muestras tu código de ese archivo? 😥
No se si esta bien hacerlo asi, pero yo me ahorre bastante codigo y siendo que queda menos confuso y automatizado quizas. Les dejo mi codigo para ver que puedo mejorar.
function Home() { const { items, searchByTitle, setSearchByTitle, filteredItems } = useContext(ShoppingCartContext) // If you want to insert a category, do it in pathAndCategory with its respective path. Category filtering does not have a state; it filters using the existing items using the values inserted in pathAndCategory. const pathAndCategories = { "/clothes": "clothing", "/electronics": "electronics", "/jewelerys": "jewelery" } const renderSearch = (items) => { if (items?.length > 0) { return ( items?.map((item) => ( <Card key={item.id} data={item} /> )) ) } else { return ( <div className=" ">No products found matching your search. Please try again with different terms.</div> ) } } const renderView = (categories) => { const areInHome = window.location.pathname === "/" const existSearch = searchByTitle?.length > 0; //In Home if (areInHome) { if (existSearch) { return ( renderSearch(filteredItems) ) } return ( items?.map((item) => ( <Card key={item.id} data={item} /> )) ) } //In Categories if (!areInHome) { //Utils let categoryOfThePath = categories[window.location.pathname]; let itemsFilterByPath = items?.filter(item => item.category.includes(categoryOfThePath)); if (searchByTitle?.length === 0) { return ( itemsFilterByPath?.map((item) => ( <Card key={item.id} data={item} /> )) ) } if (existSearch) { return ( renderSearch(filteredItems?.filter(item => item.category.includes(categoryOfThePath))) ) //Only returns items that meet the corresponding category. } } }
Para tener las categorias de manera dinamica ya que pueden cambiar en el futuro opte por useEffect(() => { fetch("https://api.escuelajs.co/api/v1/products") .then((response) => response.json()) .then((data) => { const categorias = [...new Set(data.map((item) => item.category.name))]; setItemsCategorys(categorias); return setItems(data); }); }, []);
useEffect(() => { fetch("https://api.escuelajs.co/api/v1/products") .then((response) => response.json()) .then((data) => { const categorias = [...new Set(data.map((item) => item.category.name))]; setItemsCategorys(categorias); return setItems(data); }); }, []); ```y ya salo tengo que llamar las categorías en el navbar