No tienes acceso a esta clase

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

Filtrando categorías con JavaScript

28/31
Recursos

Aportes 54

Preguntas 2

Ordenar por:

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

Hola, mi solución es algo diferente :
En App hice estos cambios a las rutas:

const AppRoutes = ()=>{
  let routes = useRoutes([
    { path:'/', element:<Home /> },
    { path:'/:category', element:<Home /> },....'

y en Home tome el valor de la rutas y luego hice el filtrado:


function Home() {
  const context = useContext(ShoppingCardContext)
  const currentPath = window.location.pathname
  let index = capitalizarPrimeraLetra(currentPath.substring(currentPath.lastIndexOf('/')+1))
  console.log(context.items)
  const renderView = ()=>{
    if(context.searchByTitle?.length > 0){
      if(context.filterItems?.length > 0){
        if(index){
          return (
            context.filterItems?.filter(item => item.category.name === index).map((item)=>(
              <Card key={item.id} data = {item}/>
           ))
          )
        }else{
      return (
          context.filterItems?.map((item)=>(
            <Card key={item.id} data = {item}/>
         ))
        )}}else{
          return(
            <div>We don't have anything</div>
          )
        }'

espero que les sirva.

Pobre Estefany se ha pegado una enredada, les comparto una forma más sencilla
en el context:

 //products array
  const [item, setItem] = useState(null);
  //searched products array
  const [searchedItem, setSearchedItem] = useState(null);
  //filtered products array
  const [filteredItems, setFilteredItems] = useState(null);

 // filter products by name, description or category
  const filtered = (item, searchedItem) => {
    return item.filter(
      (item) =>
        item.name.toLowerCase().includes(searchedItem.toLowerCase()) ||
        item.description.toLowerCase().includes(searchedItem.toLowerCase()) ||
        item.category.name.toLowerCase().includes(searchedItem.toLowerCase())
    );
  };
  // filter products by name, description or category
  useEffect(() => {
    if (searchedItem) {
      setFilteredItems(filtered(item, searchedItem));
    } else {
      setFilteredItems(item);
    }
  }, [searchedItem, item]);

en el home:

 return (
    <Layout>
      <Tittle tittle="Find Your Favorite Products" />
      <SearchInput/>
      <div className="grid gap-4  grid-cols-2 sm:grid-cols-4 w-full max-w-screen-lg px-2 mt-4">
        {context.filteredItems?.length < 1 ? (
          context.item &&
          context.item.map((item) => <Card key={item.id} data={item} />)
        ) : context.filteredItems ? (
          context.filteredItems.map((item) => (
            <Card key={item.id} data={item} />
          ))
        ) : (
          <p className="text-center text-2xl">...Loading</p>
        )}
      </div>
      <ProductDetail />
      <CheckOutSideMenu />
    </Layout>

yo lo solucione de otra manera que me resulta un poco mejor

Primero en app agregue una ruta nueva llamada category que recive el parametro :category

{path: '/category/:category', element:<Home />},

despues ya desde clases anteriores use el hook useParams(), que vi que un compañero lo recomendo, investigue un poquito y me parecio mucho mejor que obtener la url y cortarla.

<Home />

const params = useParams()  

Luego cree un estado que guarda los items segun la categoria, donde su estado inicial es la lista completa de items.

seguido con un useEffect(), compruevo si cambio params.category, y si hay category devuelvo seteo el estado con la lista de items filtrada, si no hay parametros seteo la lista con la lista completa de items.

 const {        
        items,
    }= useContext(ShoppiCartContext)

    const[itemsByCategory, setItemsByCategory] = useState(items)

    useEffect(()=>{
       if(params.category){
           setItemsByCategory(items.filter((item)=> item.category.name.toLowerCase() === params.category.toLocaleLowerCase()))
       }else{
           setItemsByCategory(items)
       }
   },[params.category])

luego para que funcione correctamente la busqueda, tenemos dos estados el de serachValue de clases anteriores que contiene el texto del input, y agregue el estado filteredItems que ai guardo la lista de items filtrados por titulo.

Para que funcione correctamente tenemos un useEffect() que comprueba si cambio el params o el search value y setea el estado filteredItems con la nueva lista de items fltrados segun categoria y buscador.

const [searchValue, setSearchValue] = useState('')

    const [filteredItems , setFilteredItems] = useState(itemsByCategory)

    useEffect(()=>{
        setFilteredItems(itemsByCategory.filter((item)=> item.title.toLowerCase().includes(searchValue.toLowerCase())))
    },[searchValue, itemsByCategory])

Luego en el map dle return tomo como lista de referencia a filteredItems, que si la lista es menor a 1 devuelve un parrafo que dice que no hubo coincidencias, pero si la lsita contiene items devuelve los items.

 {filteredItems < 1
            ?<p className="text-center text-3xl col-span-3">ups no hay coincidencias con la busqueda!</p>
            :filteredItems?.map((item)=>(<Card key={item.id} item={item} />))}

Todo el codigo seria asi

import { useContext, useEffect, useState } from "react"
import { useParams } from "react-router-dom"
import { Card } from "../../components/Card"
import { ProductDetail } from "../../components/ProductDetail"
import { CheckOutSideMenu } from "../../components/CheckOutSideMenu"
import { ShoppiCartContext } from '../../Context'



function Home (){

    const params = useParams()  

    const {        
        items,
    }= useContext(ShoppiCartContext)

    const[itemsByCategory, setItemsByCategory] = useState(items)

    useEffect(()=>{
       if(params.category){
           setItemsByCategory(items.filter((item)=> item.category.name.toLowerCase() === params.category.toLocaleLowerCase()))
       }else{
           setItemsByCategory(items)
       }
   },[params.category])

    const [searchValue, setSearchValue] = useState('')

    const [filteredItems , setFilteredItems] = useState(itemsByCategory)

    useEffect(()=>{
        setFilteredItems(itemsByCategory.filter((item)=> item.title.toLowerCase().includes(searchValue.toLowerCase())))
    },[searchValue, itemsByCategory])

return(
<section className="flex flex-col items-center justify-center gap-4">
    <input 
        type="search" 
        placeholder="buscar el producto ideal" 
        className=" w-1/2 h-8 border rounded-lg px-2 focus:outline-none"
        onChange={(e)=>setSearchValue(e.target.value)}
        />
    <section className="flex gap-1 px-2">
        <section className="  grid grid-cols-3 gap-2 w-full max-w-screen-lg">
            {filteredItems < 1
            ?<p className="text-center text-3xl col-span-3">ups no hay coincidencias con la busqueda!</p>
            :filteredItems?.map((item)=>(<Card key={item.id} item={item} />))}
        </section>
        <ProductDetail />
        <CheckOutSideMenu />
    </section>
</section>
)
}

export {Home}

Yo quería filtrar con la misma API, ya que hay una sección Filter by category pero cuando hago fetch, actualmente la API siempre regresa resultados diferentes 😕 creo que deberían controlar y organizar la BBDD

a mi me daba esta alerta al tratar de usar la funcion

la manera de solucionarlos es con esta liniea comentada

// eslint-disable-next-line react-hooks/exhaustive-deps

Antes de ver la clase lo hice de una forma diferente.
.
Me parece que es más simple de la siguiente manera.
.
En primer lugar, me parece más escalable no fijar las categorias de antemano, sino que obtenerlas de los propios productos, porque así cuando se agreguen nuevas categorias de productos, tu no tendrás que ir a crear una nueva ruta.
.
Entonces para eso lo que hice fue lo siguiente:
.

 const categoriesSet = new Set(products.map(product => product.category.name));
    const categories = Array.from(categoriesSet);

Esas dos lineas do codigo lo que hacen es generar un array donde estarán todos los nombres de las categorias, cada elemento del array es un string. Si te preguntas por qué está un “new Set” y que carajos es eso, en este caso nos sirve para que no se repitan los nombres de las categorias, sino que lo obtengamos una unica vez.
.
.
Luego en mi componente NavBar consumo ese array de categorias que lo definí en mi contexto e itero ese array para generar la información de los links de las categorias.
.

// Get the categories name from the context
    const categories = context.categories

    // Create an array of objects with the categories name.
    // That array held the data for the left side of the navbar links.
    const navLinksLeft = categories.map((category) => {
        return { to: `/${category}`, text: category }
    })

.
En mi pagina de app.jsx creé un parametro dinamico para las categorias. Esto en la ruta /
.

  let routes = useRoutes([
    { path: '/', element: <Home /> },
    { path: '/:category', element: <Home /> }, 

.
Y ahora, yendonos al home, a través del hook useParams consumo en qué categoria estamos, eso devuelve un string. PERO, si estás en el home donde están todos los productos, no hay ninguna categoria, por lo tanto el useParams te devuelve un undefined. Y si si estás en una categoria, te devuelve un string, string que usarás para filtrar los productos de solo esa categoria
.

 const { category } = useParams()

  const productsToRender = () => {
      if(!category) {
        return context.filteredProducts
    } else {
      const productsByCategory = context.filteredProducts.filter(product => product.category.name === category)
        return productsByCategory
    } 
  }

.
Dentro de la funcion “productsToRender” dependiendo si la categoria está definida o no, te devuelve una cosa u otra. Si la categoria está definida usas ese string para filtrar el array de todos los productos y obtienes solo los productos de esa categoria en especifica y los retornas.
.
Luego, por ultimo, en la interfaz llamas a la funcion “productsToRender” y ualá, listo. Ya tienes los productos filtrados.
.

<div className={`${marginRightClass} grid grid-cols-4 gap- w-full max-w-screen-lg`}>
          {productsToRender()?.map((product) => (
            <Card key={product.id} product={product} />
            ))}
        </div>

.
(contenido no patrocinado por ualá)

Mi solución fue trabajar con rutas dinámicas, en el componente app escribí lo siguiente:

const AppRoutes = () => {
  const routes = useRoutes([
    { path: "/", element: <Home /> },
    { path: "/:category", element: <Home /> },
...

Luego desde el componente Home, definí un estado category y capturé el valor del pathname y escribí la función filterByCategory que se encarga de filtrar los productos.

// Home/index.jsx
...
const [category, setCategory] = useState(null);
  
  useEffect(() => {
    setCategory(window.location.pathname.slice(1));
  },[window.location.pathname]);

  const filterByCategory = (products, category) => {
    if(category){
      return products.filter(product => product.category.name.toLowerCase() === category);
    } else {
      return products;
    }
  }

Mi solucion:

function Home() {
  const context = useContext(ShoppingCartContext);
  const { category } = useParams();

  const renderView = () => {
    if (category !== undefined) {

      const filtered = context.items.filter((item) =>
        item.category.name.toLowerCase().includes(category.toLowerCase())
      );

      if (filtered.length){
        return filtered.map((item) => <Card key={item.id} {...item} />);
      }else{
        return <div>There are nothing to see :(</div>
      }

      

    } else {
      return context.filtereditems?.map((item) => <Card key={item.id} {...item} />);
    }
  };

  return (
    <div>
      <div className="flex items-center justify-center relative w-80 mb-4">
        <h1 className="font-medium text-xl">Exclusive Products</h1>
      </div>
      <input
        type="text"
        placeholder="Search a product"
        className="rounded-lg border border-black w-80 p-4 mb-4 focus:outline-none"
        onChange={(event) => context.setSearchByTitle(event.target.value)}
      />
      <div className="grid gap-4 grid-cols-4 w-full max-w-screen-lg">
        {renderView()}
        <ProductDetail />
      </div>
    </div>
  );
}

export default Home;

noto algo curioso el filtrado ocurre si y solo si tiene caracteres el search de lo contrario muetra todas a pesar de tener categorias


.
.
.
En mi solucion no utilice el contexto mas que para obtener mis item (contex.items)
Dentro de mi componente <Home /> utilizo dos props
el campo por el cual se va a filtrar y el valor que se busca
.
.
.

en Home utilizo dos estados locales uno para controlar el cambio cambio del value del input y el otro para guardar los items filtrados y no alterar el arreglo original de items

.
.
.
ademas tengo utilizo un componente aparte para el search

.
.
.
mi funcion de filtrado filterItems() recibe un array, el campo por que se filtra y el valor que se busca

Hola, estoy haciendolo en Nextjs, y me sucede algo que tambien le sucede al proyecto de la clase, el problema de usarlo con un onClick, esque si recargamos desde la url con la ruta /clothes o las que tenemos nos sale un error, o no nos sale, alguien sabe como hacer para que no pase, digamos que en Nextjs 13 o acá, cuando se recargue la página siempre lo lleve a la ruta raiz “/”? Gracias

Hola comunidad de Platzi!!!
.
Espero se encuentren muy bien todos, les comparto mi solución:
Para esta clase lo que hice fue crear un estado que se llame renderItems y ademas
cree también dos funciones una para filtrar y otra para buscar.
.
En cada una de las funciones lo que hago es recibir como parametro un array de items,
luego con un if pregunto en el caso de la funcion para filtrar, si existen filtros y
dependiendo de ello devuelvo el mismo array o aplico los filtros. Esto mismo
hago con la funcion de buscar, dependiendo de si hay algo en el buscador devuelvo los
items buscados y si no entonces devuelvo los mismos items del argumento.
.
Por último en la parte del hook, ejecuto una función tras otra y al ultimo seteo los
items en el hook creado de renderItems. A mi parecer es más ordenado, entendible y
fácil de reutilizar. Pero estoy abierto a criticas constructivas !!!.
.
Si tienen algo que aportar se los agradeceria mucho, que tengan un excelente día de
code!

yo  hize un componente que se llama categoria y segun el path name me renderiza la categoría, pude hacerlo sin ifs pero me gusto hacerlo asi, por si acaso

import { useContext } from 'react';
import { ContextApi } from '../../Components/ContextApi';
import { Layout } from '../../Components/Layout';
import { Card } from '../../Components/Card';

const Categories = () => {
  
  const context = useContext(ContextApi);

  const currentPath = window.location.pathname.substring(window.location.pathname.lastIndexOf('/')+1); 

  let filtercategory;

  if(currentPath === 'clothes'){
    
    filtercategory = context.data.filter(item =>{
      return item.category.name.toLowerCase() === currentPath;
    });

  }else if(currentPath === 'electronics'){
    
    filtercategory = context.data.filter(item =>{
      return item.category.name.toLowerCase() === currentPath;
    });

  }else if(currentPath === 'furniture'){
    
    filtercategory = context.data.filter(item =>{
      return item.category.name.toLowerCase() === currentPath;
    });

  }else if(currentPath === 'shoes'){
    
    filtercategory = context.data.filter(item =>{
      return item.category.name.toLowerCase() === currentPath;
    });

  }else if(currentPath === 'shoes'){
    
    filtercategory = context.data.filter(item =>{
      return item.category.name.toLowerCase() === currentPath;
    });

  }else if(currentPath === 'others'){
    
    filtercategory = context.data.filter(item =>{
      return item.category.name.toLowerCase() === currentPath;
    });

  }

return (
  <Layout>
    <h1 >CATEGORY OF {currentPath.toUpperCase()}</h1>
    <div className='grid  xs:grid-cols-1 ss:grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 pt-4'>
      {
        filtercategory.map((item, index )=>{
          return (<Card categoryItem={item.category.name} idItem={item.id} descItem={item.description} imgItem={item.images} priceItem={item.price} titleItem={item.title} key={index}/>)
        })
      }
    </div>

  </Layout>
);
}

export {Categories};

Yo lo hice de esta forma donde siempre renderizamos filterProducts en Home, pero dependendiendo de la categoria en la que estemos, cambiara el searchByCategory y asi devolveremos algunos productos u otros y si la categoria es “all” devolvemos todos los productos

    const filterProducts = products.filter(product => {
        const productoName = product.title.toLowerCase()
        const productCategory = product.category.toLowerCase()
        const filterName = searchByTitle.toLowerCase()
        const filterCategory = searchByCategory.toLowerCase()

        if(productoName.includes(filterName) && filterCategory === "all") {
            return product
        } else if(productoName.includes(filterName) && productCategory === filterCategory) {
            return product

        }
    })

No sé si sea buena idea usar dos useEffects para llamar dos api, estoy usando una api diferente a la de platzi (básicamente llamo la misma pero con diferente endpoint)

La idea fue llamar la cantidad de categorias que hay automaticamente y llamarlas al navbar, adicional a ello hacer los filtros por categoria:

Use Effect

Llamando las categorias

Metodos de filtrados que los mejoré con esta clase:

Home, el renderview donde llamo las tarjetas filtradas:

Acá me gustaría compartir un ajuste para Navbar, pues voy viendo que hay categorías que no tienen datos y en los datos de la api hay categorías que no estamos alcanzando, entonces hice un navbar dinámico para las categorías:

const Navbar: FC = () => {
  const { count, items } = useContext(ShoppingCartContext);

  const getNavBarCategories = () => {
    const filteredCategories = [
      ...new Map(items.map((item) => [item.category.name, item])).values(),
    ];
    return filteredCategories.map((category) => (
      <li>
        <NavLinkStyled to={`/${category.category.name}`}>
          {category.category.name}
        </NavLinkStyled>
      </li>
    ));
  };

  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">
          <NavLinkStyled isLogo to="/">
            Shopi
          </NavLinkStyled>
        </li>
        <li>
          <NavLinkStyled to="/">All</NavLinkStyled>
        </li>
        {getNavBarCategories()}
      </ul>
      <ul className="flex items-center gap-3">
        <li className="text-black/60">[email protected]</li>
        <li>
          <NavLinkStyled to="/my-orders">My Orders</NavLinkStyled>
        </li>
        <li>
          <NavLinkStyled to="/my-account">My Account</NavLinkStyled>
        </li>
        <li>
          <NavLinkStyled to="/sign-in">Sign In</NavLinkStyled>
        </li>
        <li className="flex items-center justify-center">
          <ShoppingCartIcon className="h-6 w-6 text-green-500" />
          <div>{count}</div>
        </li>
      </ul>
    </nav>
  );
};

Acá lo importante es el getNavBarCategories

Adicional para que esto funcione, en las rutas no cree manualmente cada ruta, solo le pase en el path que iba a recibir un parámetro:

 {
      path: "/:category",
      element: <Home />,
    },

Y con eso ya en Home recupero el dato con useParams(), filtro y vualá

## ✨🦄Recordé que la API nos permitía filtrar los productos por categoría simplemente enviando en la URL el ID de la categoría que queramos. Por lo que realicé el filtrado cambiando el useEffect con el que mandamos a llamar a la API así: ![](https://static.platzi.com/media/user_upload/image-fcaadafa-498b-42e8-bd16-400162be66f4.jpg) Me percaté de que si se envía un id de 0, se retornan todos los productos sin filtrar. Así que ahora lo único que quedaba era declarar un estado global para asignar el id a filtrar. ![](https://static.platzi.com/media/user_upload/image-b2332a96-ecbd-4e3e-a6f7-3a7c838309d2.jpg) Para posteriormente cambiar los enlaces del NavBar, enviando como parámetro el nombre de la categoría de interés: ![](https://static.platzi.com/media/user_upload/image-d2f7dc48-8e7e-475f-bce6-fd13f9b2b6fb.jpg) Entonces ya solo quedaba recibir el nombre de la categoría en el home y asignar el id correspondiente al estado <u>global</u> previamente declarado en el Contexto. ![](https://static.platzi.com/media/user_upload/image-0a03ec7b-90c1-4ba2-b5dd-68b8f634e6fe.jpg) Y listo! ya se filtraron los productos por categoría gracias a la API, y el search sigue funcionando con los productos ya filtrados. ![](https://static.platzi.com/media/user_upload/image-9af07354-aa45-4b5f-aefa-c4ad2805d6af.jpg)
No sé si a alguien más le paso pero ya van dos veces que hago este curso y siempre me equivoco en sintaxis en la parte de "BY\_TITLE\_AND CATEGORY" o las demás Es desesperante pero te recomiendo seguir hasta encontrar el problema salu2, TÚ PUEDES!!! :D
Escribir comentarios con código está bien jodido :(
Hola, mi solución es diferente a la de la clase. ya que uso la misma API para filtrar por categoría, esto eventualmente consume más recursos de la API, pero simplifica la lógica del front. Yo desde el inicio del curso uso la API de [Fake Store API](https://fakestoreapi.com/docs), por lo que las categorías cambian un poco. Lo que hice fue setear la categoría desde el componente Navbar en el estado del contexto setSearchByCategory() y luego usarla en el efecto así: ```js useEffect(() => { (async function () { try { let res = null; if (searchByCategory === null || searchByCategory === "/") { res = await fetch("https://fakestoreapi.com/products"); } else { res = await fetch( `https://fakestoreapi.com/products/category/${searchByCategory}` ); } if (!res) { throw new Error("Algo falló"); } const data = await res.json(); setItems(data); } catch (error) { console.error(error); } })(); }, [searchByCategory]); ```Con el fin de que el estado de items sea dinámico, se asegure la actualización del renderizado de Home y permita buscar por título sin ningún problema.
Una manera más resumida de búsqueda por producto y categoría en el mismo useEffect: ```js // Buscador de productos const [searchInput, setSearchInput] = useState(''); // Filtro de productos const [filteredItems, setFilteredItems] = useState([]); // Categoría seleccionada const [selectedCategory, setSelectedCategory] = useState(''); // Filtrar elementos por búsqueda y categoría seleccionada useEffect(() => { let updatedItems = items; // Filtrar por categoría si hay una seleccionada if (selectedCategory) { updatedItems = items.filter( (item) => item.category.name === selectedCategory ); } // Filtrar por búsqueda si hay texto ingresado if (searchInput) { updatedItems = updatedItems.filter((item) => item.title.toLowerCase().includes(searchInput.toLowerCase()) ); } // Actualizar los items filtrados setFilteredItems(updatedItems); }, [items, searchInput, selectedCategory]); ```
```txt Este lo resolví desde la función para filtrar ``` ```js // Filter logic const filterItems = (category:string) => { if(category === "") return setFilteredItems(items); const newItems = items.filter(item => { if(category ==="Others"){ const categories = ["Electronics", "Clothes", "Furniture"] return !categories.includes(item.category.name) } return item.category.name === category; }); setFilteredItems(newItems); } ```
Para resolver el filtrado por categoría, para obtener la categoría a filtrar opté por usar Query Parameters desde la URL, haciendo un pequeño custom hook con useLocation de react-router-dom: ```js import { useLocation } from "react-router-dom"; function useQueryParameters() { const { search } = useLocation(); return new URLSearchParams(search); } export { useQueryParameters }; ```Luego para implementarlo en home: ```js // Hooks import { useQueryParameters } from "../../hooks/useQueryParameters"; function Home() { // ... let query = useQueryParameters(); useEffect(() => { setSearchByCategory(query.get("category")); }, [query]); // ... } ```Lo bueno es que con los query parameters no es necesario modificar nada del router principal, ya que estos son opcionales y no forman parte de la estructura formal de la URL.
Yo hice asi los filtrados: ```js useEffect(() => { searchByTitle && searchByCategory ? setFilteredItems( filteredItemsByTitleAndCategory(items, searchByCategory) ) : searchByTitle ? setFilteredItems(filteredItemsByTitle(items, searchByTitle)) : searchByCategory ? setFilteredItems(filteredItemsByCategory(items, searchByCategory)) : setFilteredItems(null); }, [items, searchByTitle, searchByCategory]); const filteredItemsByTitle = (items, searchByTitle) => { return items?.filter((item) => item?.title?.toLowerCase().includes(searchByTitle.toLowerCase()) ); }; const filteredItemsByCategory = (items, searchByCategory) => { return items?.filter( (item) => item?.category?.toLowerCase() === searchByCategory.toLowerCase() ); }; const filteredItemsByTitleAndCategory = (items, searchByCategory) => { const categoryFiltered = filteredItemsByCategory(items, searchByCategory); return categoryFiltered.filter((item) => item?.title?.toLowerCase().includes(searchByTitle.toLowerCase()) ); }; ```

Hola, es importante decir que react-router-dom es capaz de recordar la página donde estabamos si se recarga el navegador, pero si se guarda la última categoría en el estado al recargar la página se pierde esa información. Una forma de solucionar es usar localStorage.setItem, guardar ahí la última categoría cuando se haga click y luego en el contexto podemos crear una función que consulte a localStorage.
Luego el siguiente paso clave es lo siguiente, y por eso lo importante de este aporte, puedes iniciar tu estados con una función. Por ejemplo:

const getLastCategory = () => {
  const lastCategory = localStorage.getItem("lastCategory");
  return !lastCategory ? categories.all : lastCategory;
};
<code> 

Luego la llamamos así en el useState(getLastCategory), no coloquen getLastCategory() porque va a ejecutarse cada vez que el componente se renderice, en cambio si se coloca getLastCategory simplemente se ejecuta cuando el componente se monta por primera vez y de esa manera el estado de la categoría va iniciar con última categoría visitada.

En esta clase quedé más perdido que quien sabe que, pero al final pude entender y solucionar los errores que tenía en mi código. En el NavBar tuve que crear una función para validar la URL, ya que no me estaba mostrando los productos cuando regresaba al home. ```js const checkPathName = (link, name) => { if (name == "All" || name == "Shopi") { setSearchByCategory(); } else { setSearchByCategory(link); } }; ```Y el NavLink quedó así: ```js
    {left_items.map(({ path_link, path_name }, index) => (
  • <NavLink to={path_link} className={handleActive} onClick={() => { checkPathName(path_link, path_name); }} > {path_name} </NavLink>
  • ))}
```
Yo lo hice un tanto diferente, al estar usando el api de <https://fakestoreapi.com>, me traigo las categorías de su endpoint y construyo el navbar forma dinámica, utilizando un estado. ```js ... { context.categories.map(category => (
  • <NavLink to={`/${category}`} className={({ isActive }) => isActive ? activeStyle : undefined } onClick={()=>context.setCategory(category)} > {category} </NavLink>
  • )) } .... ```La ruta la hice con un parámetro y así es mas sencillo, dependerá de las que vengan del api ```js ... {path: '/:category', element: <Home />}, ... ```También cree un estado para almacenar la categoría seleccionada y cambie el efecto de buscar por el input añadiendo el estado de la categoría que cambia con el navbar ```js ... useEffect(() => { setFilteredItems(items) if (searchTerm && category) { const categoryItems = filterItems(items, category, 'category'); setFilteredItems(filterItems(categoryItems, searchTerm, 'title')); } else { if (searchTerm) setFilteredItems(filterItems(items, searchTerm)); if (category) setFilteredItems(filterItems(items, category, 'category')); } }, [items, searchTerm, category]); ... ```Y en el home uso siempre filteredItems, ya que o tiene todos, o tiene los filtrados por categoría o tiene los filtrados por el input Y como comente en el video pasado, cree la función de filtrado mas general para usarla en cualquiera de los casos que se tienen ```js const filterItems = (items, searchTerm, filterField='all') => { const term = searchTerm?.toLowerCase().trim() || ""; return filterField === 'all' ? items.filter(item => item.title.toLowerCase().includes(term) || item.category.toLowerCase().includes(term) || item.description.toLowerCase().includes(term)) : items.filter(item => item[filterField].toLowerCase().includes(term)); } ```
    En mi caso filtre los productos por categoría usando la misma API de la siguiente manera: El primer cambio que hice fue en el componente App ```tsx // App.tsx <Route path='/' element={<Home />} /> <Route path='/:category' element={<Home />} /> ```Así puedo capturar en el componente Home la categoría como parámetro, usando el hook de react-router useParams(). ```tsx // Home.tsx enum Categories { clothes = 1, electronics = 2, furnitures = 3, shoes = 4, miscelaneous = 5, } const params = useParams() let categoryId: number | undefined if (!params.category) { categoryId = 0 } else { categoryId = Categories[params.category as keyof typeof Categories] } ```La API permite filtrar por categoría poniendo el id de la misma, entonces tengo que convertir el nombre de la categoría en su correspondiente id, para esto cree un Enum llamado Categories, que uso en el condicional anterior. Ya con el id de la categoría, simplemente es armar la petición a la API ```tsx useEffect( () => { if (categoryId !== undefined) { if (categoryId === 0) { getDataFromApi('https://api.escuelajs.co/api/v1/products?offset=0&limit=20') } else { getDataFromApi(`https://api.escuelajs.co/api/v1/products/?categoryId=${categoryId}&offset=0&limit=20`) } } }, [categoryId]) ```Ya finalmente aclaro que cuando useParams.category me retorna undefined, es porque la ruta no tiene ninguna categoría, y por lo tanto tengo que traer todos los productos. Para este caso asigné como categoryId = 0, y puse un caso especial en el useEffect. Si categoryId es undefined, es porque si se hubo una categoría, pero no hizo match con mi Enum.
    mi gente les recomiendo que simplemente cambien en el home la función renderView en vez de la función renderBy y el cambio en el useEffect, asi quedó mi render view: const renderView = ()=>{    if (!context.items) {        return (            \
                    \<ArrowPathIcon className="w-1/2 h-1/2 text-blue-500" />                \

    Loading...\

                \
            )    }     let filteredItems = context.items;     *// Filtrar por título si searchByTitle tiene algún valor*    if (context.searchByTitle) {        filteredItems = filteredItems.filter(item => item.title.toLowerCase().includes(context.searchByTitle.toLowerCase()));    }     *// Filtrar por categoría si searchByCategory tiene algún valor*    if (context.searchByCategory) {        filteredItems = filteredItems.filter(item => item.category === context.searchByCategory);    }     *// Si no hay elementos después de filtrar, mostrar "No Matches"*    if (filteredItems.length === 0) {        return (            \
                    \<FaceFrownIcon className="w-1/2 h-1/2 text-blue-500" />                \

    No Matches\

                \
            )    }     *// Renderizar los elementos filtrados*    return filteredItems.map((item) => \<Card key= {item.id} data={item}/>);} ```js const renderView = ()=>{ if (!context.items) { return (
    <ArrowPathIcon className="w-1/2 h-1/2 text-blue-500" />

    Loading...

    ) } let filteredItems = context.items; // Filtrar por título si searchByTitle tiene algún valor if (context.searchByTitle) { filteredItems = filteredItems.filter(item => item.title.toLowerCase().includes(context.searchByTitle.toLowerCase())); } // Filtrar por categoría si searchByCategory tiene algún valor if (context.searchByCategory) { filteredItems = filteredItems.filter(item => item.category === context.searchByCategory); } // Si no hay elementos después de filtrar, mostrar "No Matches" if (filteredItems.length === 0) { return (
    <FaceFrownIcon className="w-1/2 h-1/2 text-blue-500" />

    No Matches

    ) } // Renderizar los elementos filtrados return filteredItems.map((item) => <Card key= {item.id} data={item}/>); } ```

    Día 12/01/24 la api esta siendo modificada, que hermoso era los días anteriores. Encima todavia no se tanto como para consumir otra api, quise cambiarla pero se me rompió el código y lo dejé mejor como estaba.

    Mi solución al problema de que no renderiza los products al cambiar de category, fue esta: En Context ```js useEffect(() => { if (searchByTitle) setFilteredItems(filteredItemsByTitle(items, searchByTitle)); if (searchByCategory) { setFilteredItems(filteredItemsByCategory(items, searchByCategory)); } else { setFilteredItems(null); ``//asegurará que filteredItems se borre cada vez que cambie de categoría.`` } }, [items, searchByTitle, searchByCategory]); ```Y en Home: ```js const renderView = () => { if (context.searchByTitle?.length > 0 || context.filteredItems?.length > 0) {``//si filteredItems es null, return all products`` if (context.filteredItems?.length > 0) { return context.filteredItems?.map((``item``) => <Card ``key``={``item``.id} ``product``={``item``} />); } else { return
    No hay coincidencias
    ; } } else { return context.items?.map((``item``) => <Card ``key``={``item``.id} ``product``={``item``} />); } }; ```
    Mi solución al problema de que no renderiza los products al cambiar de category, fue esta: En Context `  useEffect(() => {    if (searchByTitle) setFilteredItems(filteredItemsByTitle(items, searchByTitle));    if (searchByCategory) {      setFilteredItems(filteredItemsByCategory(items, searchByCategory));    } else {      setFilteredItems(null); ``//asegurará que filteredItems se borre cada vez que cambie de categoría.``    }  }, [items, searchByTitle, searchByCategory]);` Y en Home:  `const renderView = () => {    if (context.searchByTitle?.length > 0 || context.filteredItems?.length > 0) {``//si filteredItems es null, return all products``      if (context.filteredItems?.length > 0) {        return context.filteredItems?.map((``item``) => <Card ``key``={``item``.id} ``product``={``item``} />);      } else {        return
    No hay coincidencias
    ;      }    } else {      return context.items?.map((``item``) => <Card ``key``={``item``.id} ``product``={``item``} />);   }  };`
    ```js const filteredItems =data?.filter(todo=>{ //filtramos los elementos por titulos const titleMatch = todo.title.toLowerCase().includes(searchByTitle.toLowerCase()) //filtramos por las categorias const categoryMatch = searchByCategory.toLowerCase() === 'all' || todo.category.toLowerCase().includes(searchByCategory.toLowerCase()); return titleMatch && categoryMatch; }) ```
    `"`const filteredItems =data?.filter(todo=>{    //filtramos los elementos por titulos   const titleMatch = todo.title.toLowerCase().includes(searchByTitle.toLowerCase())   //filtramos por las categorias   const categoryMatch = searchByCategory.toLowerCase() === 'all' ||   todo.category.toLowerCase().includes(searchByCategory.toLowerCase());    return titleMatch && categoryMatch;}) `"`
    `const filteredItems =data?.filter(todo=>{    //filtramos los elementos por titulos   const titleMatch = todo.title.toLowerCase().includes(searchByTitle.toLowerCase())` `  //filtramos por las categorias   const categoryMatch = searchByCategory.toLowerCase() === 'all' ||   todo.category.toLowerCase().includes(searchByCategory.toLowerCase());    return titleMatch && categoryMatch;})`
    Encontré una forma un poco menos complicado de implementar las funcionalidades de la app : en la parte del context utilice dos estados, uno para almacenar lo que se vaya escribiendo en la barra de búsqueda, y el otro para almacenar los nombres de las categorías : ```js //Almacenamiento para los valores en la barra de busqueda const [searchValue, setSearchValue] = useState('') //Estado para almacenar el nombre de las rutas por categoria const [categoryPath, setCategoryPath] = useState('') ```*//Almacenamiento para los valores en la barra de busqueda*    const \[searchValue, setSearchValue] = useState('')     *//Estado para almacenar el nombre de las rutas por categoria*    const \[categoryPath, setCategoryPath] = useState('') Después utilice dos variables, el primero para almacenar el filtro por categoría, y la otra para el filtro entre categoría y lo que se escribe en la barra de búsqueda: ```js //Variable para almacenar el filtro los elementos a partir de las categorias const searcheadCategories = items?.filter((item) =>{ const searchCategoryText = categoryPath?.toLowerCase() return item.category.toLowerCase().includes(searchCategoryText) }) //Variable para renderizar los productos que coincidan en categoria y en la barra de busqueda const renderPages = searcheadCategories?.filter((item) => { const searchValueText = searchValue?.toLowerCase() return item.title.toLowerCase().includes(searchValueText) }) ```  *//Variable para almacenar el filtro los elementos a partir de las categorias*    const searcheadCategories = items?.filter((*item*) =>{        const searchCategoryText = categoryPath?.toLowerCase()            return item.category.toLowerCase().includes(searchCategoryText)    })        *//Variable para renderizar los productos que coincidan en categoria y en la barra de busqueda*    const renderPages = searcheadCategories?.filter((*item*) => {        const searchValueText = searchValue?.toLowerCase()         return item.title.toLowerCase().includes(searchValueText)    }) Finalmente paso la ultima variable 'renderPages' a Home para renderizar las coincidencias. no se si estaré cometiendo algún pecado para React, espero cualquier tipo de recomendación. El context entero quedaría así : ```js import { createContext, useState, useEffect } from 'react'; const ShoppingCartContext = createContext(); function ShoppingCartProvider({children}) { //Estado y funciones para manejar el product detail const [isOpenDetails, setisOpenDetails] = useState(false) const openDetails = () => setisOpenDetails(true) const closeDetails = () => setisOpenDetails(false) //Estado y funciones para manejar el Cheackout side menu const [isOpenCheckout, setisOpenCheckout] = useState(false) const openCheckout = () => setisOpenCheckout(true) const closeCheckout = () => setisOpenCheckout(false) //Estado para enviar y mostrar la información de la card al product detail const [productToShow, setProductToShow] = useState({}) //Estado para agrupar los productos del checkout menu const [cartProducts, setCartProducts] = useState([]) //Funcion para eliminar productos del checkout menu const handleDelete = (id) => { const newCartProducts = cartProducts.filter(product => product.id != id) setCartProducts(newCartProducts) } //Funcion para aumentar la cantidad de productos const increaseQuantities = (id) => { const productData = cartProducts.find((product) => product.id === id) productData.quantity += 1 setCartProducts([...cartProducts]) } //Funcion para disminuir la cantidad de productos const decreaseQuantities = (id) => { const productData = cartProducts.find((product) => product.id === id) productData.quantity -= 1 setCartProducts([...cartProducts]) if(productData.quantity === 0){ handleDelete(id) } } //Estado para agrupar la informacion del shopping cart const [order, setOrder] = useState([]) //Funcion para agupar la informacion de cada orden const handleCheckout = (totalPrice) => { const currentDate = () => { const date = new Date().toLocaleDateString(); return date } const addToOrder = { date: currentDate(), products: cartProducts, totalProducts: cartProducts.length, totalPrice: totalPrice(cartProducts), } setOrder([...order, addToOrder]) setCartProducts([]) closeCheckout() setSearchValue('') } //Manejo de la API y el estado que maneja la información const API = 'https://fakestoreapi.com/products'; const [items, setItems] = useState(null); useEffect(() => { fetch(API) .then(response => response.json()) .then(data => setItems(data)) .catch(error => console.log(error)) }, []) //Almacenamiento para los valores en la barra de busqueda const [searchValue, setSearchValue] = useState('') //Estado para almacenar el nombre de las rutas por categoria const [categoryPath, setCategoryPath] = useState('') //Variable para almacenar el filtro los elementos a partir de las categorias const searcheadCategories = items?.filter((item) =>{ const searchCategoryText = categoryPath?.toLowerCase() return item.category.toLowerCase().includes(searchCategoryText) }) //Variable para renderizar los productos que coincidan en categoria y en la barra de busqueda const renderPages = searcheadCategories?.filter((item) => { const searchValueText = searchValue?.toLowerCase() return item.title.toLowerCase().includes(searchValueText) }) return( <ShoppingCartContext.Provider value={{ openDetails, closeDetails, isOpenDetails, openCheckout, closeCheckout, isOpenCheckout, productToShow, setProductToShow, cartProducts, setCartProducts, increaseQuantities, handleDelete, decreaseQuantities, order, setOrder, handleCheckout, items, setItems, searchValue, setSearchValue, categoryPath, setCategoryPath, searcheadCategories, renderPages, }}> {children} </ShoppingCartContext.Provider> ) } export {ShoppingCartContext, ShoppingCartProvider}; ```import { createContext, useState, useEffect } from 'react'; const ShoppingCartContext = createContext(); function ShoppingCartProvider({*children*}) {     *//Estado y funciones para manejar el product detail*    const \[isOpenDetails, setisOpenDetails] = useState(false)    const openDetails = () => setisOpenDetails(true)    const closeDetails = () => setisOpenDetails(false)     *//Estado y funciones para manejar el Cheackout side menu*    const \[isOpenCheckout, setisOpenCheckout] = useState(false)    const openCheckout = () => setisOpenCheckout(true)    const closeCheckout = () => setisOpenCheckout(false)     *//Estado para enviar y mostrar la información de la card al product detail*    const \[productToShow, setProductToShow] = useState({})     *//Estado para agrupar los productos del checkout menu*    const \[cartProducts, setCartProducts] = useState(\[])     *//Funcion para eliminar productos del checkout menu*    const handleDelete = (*id*) => {        const newCartProducts = cartProducts.filter(*product* => product.id != id)        setCartProducts(newCartProducts)    }     *//Funcion para aumentar la cantidad de productos*    const increaseQuantities = (*id*) => {        const productData = cartProducts.find((*product*) => product.id === id)        productData.quantity += 1         setCartProducts(\[...cartProducts])    }     *//Funcion para disminuir la cantidad de productos*    const decreaseQuantities = (*id*) => {        const productData = cartProducts.find((*product*) => product.id === id)        productData.quantity -= 1        setCartProducts(\[...cartProducts])                if(productData.quantity === 0){            handleDelete(id)        }             }     *//Estado para agrupar la informacion del shopping cart*    const \[order, setOrder] = useState(\[])        *//Funcion para agupar la informacion de cada orden*    const handleCheckout = (*totalPrice*) => {         const currentDate = () => {            const date = new Date().toLocaleDateString();            return date         }         const addToOrder = {            date: currentDate(),            products: cartProducts,            totalProducts: cartProducts.length,            totalPrice: totalPrice(cartProducts),        }        setOrder(\[...order, addToOrder])         setCartProducts(\[])        closeCheckout()        setSearchValue('')    }     *//Manejo de la API y el estado que maneja la información*    const API = 'https://fakestoreapi.com/products';     const \[items, setItems] = useState(null);        useEffect(() => {        fetch(API)            .then(*response* => response.json())            .then(*data* => setItems(data))            .catch(*error* => console.log(error))    }, \[])     *//Almacenamiento para los valores en la barra de busqueda*    const \[searchValue, setSearchValue] = useState('')     *//Estado para almacenar el nombre de las rutas por categoria*    const \[categoryPath, setCategoryPath] = useState('')     *//Variable para almacenar el filtro los elementos a partir de las categorias*    const searcheadCategories = items?.filter((*item*) =>{        const searchCategoryText = categoryPath?.toLowerCase()            return item.category.toLowerCase().includes(searchCategoryText)    })        *//Variable para renderizar los productos que coincidan en categoria y en la barra de busqueda*    const renderPages = searcheadCategories?.filter((*item*) => {        const searchValueText = searchValue?.toLowerCase()         return item.title.toLowerCase().includes(searchValueText)    })     return(        \<ShoppingCartContext.Provider value={{            openDetails,            closeDetails,            isOpenDetails,            openCheckout,            closeCheckout,            isOpenCheckout,            productToShow,            setProductToShow,            cartProducts,            setCartProducts,            increaseQuantities,            handleDelete,            decreaseQuantities,            order,             setOrder,            handleCheckout,            items,             setItems,            searchValue,             setSearchValue,            categoryPath,             setCategoryPath,            searcheadCategories,            renderPages,        }}>            {children}        \</ShoppingCartContext.Provider>    )} export {ShoppingCartContext, ShoppingCartProvider};

    Yo pienso que es más fácil crear un componente para cada vista de categorías (Aunque podría ser mejor alguna función que nos filtre si tenemos muchas categorias)

    import { useContext } from "react";
    import { Card } from "../../Components/Card/Card";
    import { Layout } from "../../Components/Layout/Layout";
    import { ShoppingCartContext } from "../../Context/Context";
    
    
    
    function ClothesPage() {
        const context = useContext(ShoppingCartContext);
        return (
            <Layout>
               <p className="text-white">Clothes</p>
                <div className="grid grid-cols-4 gap-4 mt-10 w-full max-w-screen-lg">
              {
                context.items?.map( (item)=> (
                item.category.name === "Clothes" &&  <Card data={item} key={item.id}/>
    // Lo único que hago es en cada componente ver si en los items hay un item con el nombre de la categoría deseada y lo renderizo
                ) )
              }
              </div>
            </Layout>
        )
    }
    
    
    export {ClothesPage}
    

    Les comparto mi solución:

    Asi como realice cambio en Navbar para pasarle la categoria y un manejador del click al NavLink que al principio de este curso habiamos generado un componente llamado NavItem


    resultando mi Navbar de la siguiente forma

    Mi solucion es la siguiente: Primero agrego una nueva ruta para las categorias en App.jsx ```js { path: '/', element: <Home /> }, { path: '/category/:category', element: <Home /> }, ```En Home: ```js const { category } = useParams(); // console.log(category); const categoryName = category?.replace('-', ' ').replace('mens', "men's"); const filteredByCategory = category ? searchedProducts?.filter(product => product.category === categoryName) : searchedProducts; ```Finalmente en el return se hace map a `filteredByCategory`
    Les dejo la solución a la que llegamos chatgpt y yo con useParams😆 En mi caso estoy utilizando la fakestore api <https://fakestoreapi.com/> la cual contiene categorías con apóstrofos y espacios (men's clothing y women's clothing) Navbar: ```js import React from 'react'; import { NavLink } from 'react-router-dom'; import { ShoppingCartContext } from '../../Context'; import { ShoppingBagIcon } from '@heroicons/react/24/solid'; const Navbar = () => { const { cartProducts, setOpenCheckoutSM, } = React.useContext(ShoppingCartContext); const normalizeCategory = (category) => { return category.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '-'); }; const categories = [ "electronics", "jewelery", "men's clothing", "women's clothing" ]; const activeStyle = 'underline underline-offset-4'; return ( <nav className='bg-white flex justify-between items-center fixed z-10 top-0 w-full py-5 px-8 text-sm font-light'>
    • <NavLink to='/'> Shopi </NavLink>
    • {categories.map(category => (
    • <NavLink to={`/category/${normalizeCategory(category)}`} className={({ isActive }) => isActive ? activeStyle : undefined } > {category} </NavLink>
    • ))}
    • [email protected]
    • <NavLink to='/my-orders' className={({ isActive }) => isActive ? activeStyle : undefined }> My Orders </NavLink>
    • <NavLink to='/my-account' className={({ isActive }) => isActive ? activeStyle : undefined }> My Account </NavLink>
    • <NavLink to='/sign-in' className={({ isActive }) => isActive ? activeStyle : undefined }> Sign In </NavLink>
    • <ShoppingBagIcon className='w-6 h-6' onClick={() => setOpenCheckoutSM(state => !state)} />
      {cartProducts.length}
    </nav> ) } export default Navbar ```En la función `normalizeCategory`, he ajustado la expresión regular para permitir espacios (`\s`) y eliminado el reemplazo de espacios con guiones bajos adicionales. Esto debería ayudar a mantener los apóstrofos y espacios correctamente. Componente Home: ```js import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import Layout from '../../Components/Layout' import Card from '../../Components/Card' import ProductDetail from '../../Components/ProductDetail' import Modal from '../../Components/Modal'; import { ShoppingCartContext } from '../../Context' function Home() { const { items, openModal, } = React.useContext(ShoppingCartContext); const { category } = useParams(); const [itemsByCategory, setItemsByCategory] = useState([]); const [searchValue, setSearchValue] = useState(''); const [filteredItems, setFilteredItems] = useState([]); useEffect(() => { const categoryLower = category?.toLowerCase(); console.log('Category:', categoryLower); const filteredItems = categoryLower ? items.filter((item) => item.category && item.category.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '-') === categoryLower) : items; console.log('Filtered Items:', filteredItems); setItemsByCategory(filteredItems); }, [category, items]); useEffect(() => { setFilteredItems( itemsByCategory.filter((item) => item.title && item.title.toLowerCase().includes(searchValue.toLowerCase())) ); }, [searchValue, itemsByCategory]); return ( <Layout> <h1 className='mt-3 font-medium text-2xl'>Exclusive Products <input type="text" placeholder='Looking for a product?' className='rounded-lg border-black w-1/3 p-4 mb-4 focus:outline-none' onChange={(event) => setSearchValue(event.target.value)} />
    {filteredItems.length < 1 ? (

    Ups, no hay coincidencias con la búsqueda.

    ) : ( filteredItems.map((item) => <Card key={item.id} data={item} />) )}
    {openModal && ( <Modal> <ProductDetail /> </Modal> )} </Layout > ) } export default Home ```En el `useEffect` de `Home`, he ajustado la comparación para que también normalice la categoría antes de compararla con la categoría de los elementos. Esto garantiza que las comparaciones se realicen en un formato consistente. Espero a alguien le pueda servir mi aporte!

    Yo lo hice como lo nos explicó Jenny, pero aún así no me filtraba a pesar de seleccionar las categorías, tuve que realizar estas modificaciones y así si me funcionó:

    CONTEXT

        const filterBy = (searchType, items, searchByTitle, searchByCategory) =>{
    
            if (searchType ==="BY_TITLE"){
                return filteredItemsByTitles(items, searchByTitle)
            }
    
            if (searchType ==="BY_CATEGORY"){
                return filteredItemsByCategory(items, searchByCategory)
            }
    
            if (searchType ==="BY_TITLE_AND_CATEGORY"){
                return filteredItemsByCategory(items, searchByCategory).filter(item => item.title.toLowerCase().includes(searchByTitle.toLowerCase()))
            }
    
            if (searchType === "NULO"){
                return items
            }
        }
    
        useEffect(()=>{
            if (searchByTitle && !searchByCategory) setFilteredItems(filterBy("BY_TITLE", items, searchByTitle, searchByCategory))
            if (searchByCategory && !searchByTitle) setFilteredItems(filterBy("BY_CATEGORY", items, searchByTitle, searchByCategory))
            if (!searchByCategory && !searchByTitle) setFilteredItems(filterBy("NULO", items, searchByTitle, searchByCategory))
            if (searchByCategory && searchByTitle) setFilteredItems(filterBy("BY_TITLE_AND_CATEGORY", items, searchByTitle, searchByCategory))
    
        }, [items, searchByTitle, searchByCategory])
    

    HOME:

    const renderView =() =>{
      if (searchByTitle?.length > 0){
        if (filteredItems?.length > 0){
          return(
            filteredItems?.map(item => (
              <Card key={item.id} data={item} />))
          )
        }else{
          return(
            <div>We don´t have anything :(</div>
          )
        }
      }else{
        return (
          filteredItems?.map(item => (
            <Card key={item.id} data={item} />
          ))
        
        )
    
      }
    }
    
    ```js ///asi con cada categoria function Electronics() { const context = useContext(ContextShoppingCart); const filteredItems = context.items.filter((item)=> item.category.includes('electronics')); console.log(filteredItems); return ( <Layout>

    Electronics

    { filteredItems?.map(item =>( <Card key={item.id} item={item}/> )) }
    <ProductDetail /> </Layout> ) } export {Electronics} ```function Electronics() {    const context = useContext(ContextShoppingCart);  const filteredItems = context.items.filter((item)=> item.category.includes('electronics'));   console.log(filteredItems);    return (    \<Layout>       \
              \

    Electronics\

            \
          \
            {          filteredItems?.map(item =>(            \<Card key={item.id} item={item}/>          ))        }         \
            \
          \
          \<ProductDetail />    \</Layout>  )} export {Electronics}
    Este curso no esta del todo bien dado y se nota un poco del que lo da que no esta bien preparado el tema (más que nada con esta clase) Se enreda mucho en algo que con un route y un buen manejo de API con el loading se resuelve...pero lo bueno que no esté del todo bien dado, es que te obliga a aprender por tu cuenta Dejo como hice lo mio en github: <https://github.com/Momfus/react-shop-con-vite-tailwindcss>
    Yo cambie un poco el código y lo reduje a lo siguiente ```js const filterItems = (items, searchBy, key) => { return items?.filter((item) => { const value = key.split('.').reduce((obj, k) => obj?.[k], item); return value && value.toLowerCase().includes(searchBy.toLowerCase()); }); }; ``````js const filterBy = ({ searchType, items, searchByTitle, searchByCategory }) => { if (searchType === 'BY_TITLE') { return filterItems(items, searchByTitle, 'title'); } if (searchType === 'BY_CATEGORY') { return filterItems(items, searchByCategory, 'category.name'); } if (searchType === 'BY_TITLE_AND_CATEGORY') { return filterItems( filterItems(items, searchByCategory, 'category.name'), searchByTitle, 'title' ); } return items; }; ``````js useEffect(() => { let searchType = null; if (searchByTitle && searchByCategory) searchType = 'BY_TITLE_AND_CATEGORY'; else if (searchByTitle) searchType = 'BY_TITLE'; else if (searchByCategory) searchType = 'BY_CATEGORY'; setFilteredItems( filterBy({ searchType, items, searchByTitle, searchByCategory, }) ); }, [items, searchByTitle, searchByCategory]); ```
    Yo cambie un poco el código y lo reduje a lo siguiente ![](/home/nick/Imágenes/code.png)![](file:///home/nick/Im%C3%A1genes/code.png)```js const filterItems = (items, searchBy, key) => { return items?.filter((item) => { const value = key.split('.').reduce((obj, k) => obj?.[k], item); return value && value.toLowerCase().includes(searchBy.toLowerCase()); }); }; const filterBy = ({ searchType, items, searchByTitle, searchByCategory }) => { if (searchType === 'BY_TITLE') { return filterItems(items, searchByTitle, 'title'); } if (searchType === 'BY_CATEGORY') { return filterItems(items, searchByCategory, 'category.name'); } if (searchType === 'BY_TITLE_AND_CATEGORY') { return filterItems( filterItems(items, searchByCategory, 'category.name'), searchByTitle, 'title' ); } return items; }; useEffect(() => { let searchType = null; if (searchByTitle && searchByCategory) searchType = 'BY_TITLE_AND_CATEGORY'; else if (searchByTitle) searchType = 'BY_TITLE'; else if (searchByCategory) searchType = 'BY_CATEGORY'; setFilteredItems( filterBy({ searchType, items, searchByTitle, searchByCategory, }) ); }, [items, searchByTitle, searchByCategory]); ```

    Buenassss
    Mi solución:

    Facilite y cambien un poco la logica de los filtros para que se mas facil agregar mas filtros en un futuro

        const filterItemsMultiple = (items, searchByTitle, searchByCategory) => {
            let newFilter = items
            
            if (searchByTitle) {
                newFilter = newFilter?.filter(item => item.title.toLowerCase().includes(searchByTitle.toLowerCase()))
            }
    
            if (searchByCategory) {
                newFilter = newFilter?.filter(item => item.category.id === searchByCategory )
            }
            return newFilter
        }
    
        useEffect(() => {
            setFilteredItems(filterItemsMultiple(items, searchByTitle, searchByCategory))
        }, [items, searchByTitle, searchByCategory])
    

    y en vez de mandar el string de la categoria mande el id de la categoria

    EJ:

    onClick={() => context.setSearchByCategory(null)}
    onClick={() => context.setSearchByCategory(1)}
    onClick={() => context.setSearchByCategory(2)}
    onClick={() => context.setSearchByCategory(3)}
    onClick={() => context.setSearchByCategory(4)}
    onClick={() => context.setSearchByCategory(5)}
    

    y del lado del componente home

    const Home = () => {
        
        const context = useContext(ShoppingCartContext)
    
        const renderView = () => {
          const items = context.searchByTitle?.length > 0 || context.searchByCategory ?
            context.filteredItems :
            context.items
          
    
          if (items?.length > 0) {
            return (
              items?.map(
                (item) => (<Card key={item.id} data={item} />)
              )
            )
          } else {
            return (
              <p className='font-medium text-xl'> No result Found </p>
            )
          }
        }
    
        return (
          <Layout>
            <div className='flex items-center justify-center w-80 relative mb-4'>
              <h1 className='font-medium text-xl'>Exclusive products</h1>
            </div>
            <input 
              type="text" 
              placeholder='Search a product' 
              className='border border-black rounded-lg w-80 p-3 mb-4 focus:outline-none'
              onChange={(event) => context.setSearchByTitle(event.target.value) }
            />
            <div className='grid gap-4 grid-cols-4 w-full max-w-screen-lg'>
            {renderView()}
            </div>
            <ProductDetail />
          </Layout>
        )
      }
    

    Otra solución que hace prácticamente lo mismo:

    // En la página Home
    export function Home() {
      const currentPath = window.location.pathname;
      const tableIndex = currentPath.split("/"); // ['', 'clothes']
      const categoryName = tableIndex[tableIndex.length - 1];
      const isInAllCategory = categoryName === "";
    
      let productsToRender: Product[] = [];
    
      const searchForMatches = (listOfProducts: Product[]) => {
        const filteredProducts = listOfProducts.filter((product) => {
          const productTitle = product.title.toLowerCase();
          const searchTitle = searchTitleProduct.toLowerCase();
          return productTitle.includes(searchTitle);
        });
    
        return filteredProducts;
      };
    
      if (isInAllCategory) {
        productsToRender = searchForMatches(products);
      } else {
        const categoryProducts = products.filter((product) => {
          const productCategory = product.category.name.toLowerCase();
          const categoryToSearch = categoryName.toLowerCase();
          return productCategory.includes(categoryToSearch);
        });
    
        productsToRender = searchForMatches(categoryProducts);
      }
    
      return (
        <>
          <div className="grid md:grid-cols-3 lg:grid-cols-4 gap-4">
            {(productsToRender?.length === 0)
              ? <p>No products found :(</p>
              : productsToRender?.map((product) => (
                <Card key={product.id} {...product} />
              ))}
          </div>
        </>
      );
    }
    

    este fue el metodo en el componente
    home

     let category = context.selectedNavItem === "All" ? null : context.selectedNavItem;
      let filteredItems = items;
    
      if (category) {
        filteredItems = items?.filter(
          (item) =>
            item.category.toLowerCase() === category.toLowerCase() &&
            item.title.toLowerCase().includes(searchByTitle?.toLowerCase())
        );
      } else {
        filteredItems = items?.filter((item) =>
          item.title.toLowerCase().includes(searchByTitle?.toLowerCase())
        );
      }
    

    y la cateogria se toma con un click debido a como tengo planteado mi codigo

    adicionalmente el state de searchByTitle se definio como string vacio

      const [searchByTitle, setSearchByTitle] = useState("");
    
    

    por eso si se deja el imput vacio y tiene cateogria aun sigue filtrando

    Simplificacion de codigo
    Pongan todo lo que tiene que ver con:

    • Filtrado por busqueda
    • Filtrado por categoria
    • Funcion renderizadora
      Dentro del componente HOME* y mejor usen su tiempo en hacer que la app sea vea mas bonita. =)

    Qué sucedió aquí?

    Yo estoy usandolo desde Context, los dejo por si les sirve a alguien de referencia.

    Declare estos useContext

    const { searchByTitle, setSearchByTitle, filteredItems, items, filteredItemsByCategory, setFilteredItemsByCategory } = context
    

    En renderView, añadí la siguiente lógica

    const renderView = () => {
    
        const pathSplitted = window.location.pathname.split('/');
        let category = pathSplitted[pathSplitted.length - 1];
    
        const FilteredItemsByCategory = (items, category ) => {
            if (category.length === 0) {
              return items?.map(item => item)        
            } else {
              return items?.filter((item) => item.category.name.toLowerCase().replace(/\s/g, '') === category)
            }
        }
    
        useEffect(() => {
          setFilteredItemsByCategory(FilteredItemsByCategory(items, category))
        }, [category])
    
        if (searchByTitle?.length > 0) {
          if (filteredItems?.length > 0) {
                return (
                  filteredItems?.map(item => (
                    <Card key={item.id} data={item} />
                  ))
                )
              } else {
                return (
                  <div className='flex items-center justify-center mt-10 w-screen'>
                    <p className="text-center">We could not find it</p>
                  </div>
                )
          }
        } else {
            return (
              filteredItemsByCategory?.map(item => (
                <Card key={item.id} data={item} />
              ))
            )
          }
      }
    

    Yo utilicé 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;