No tienes acceso a esta clase

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

Filtrando títulos con JavaScript

27/31
Recursos

Aportes 38

Preguntas 3

Ordenar por:

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

Simplifiquemos el código:

const renderView = () => {
    const itemsToRender = context.SearchByTitle?.length > 0
      ? context.FilteredItems
      : context.Items;

    if (itemsToRender?.length > 0) {
      return itemsToRender.map(item => (
        <Card key={item.id} data={item} />
      ));
    } else {
      return <p>No Results Found</p>;
    }
  };

Yo tengo esta solución, que me pareció mas simple .

  • Darle como valor inicial un string vacío
const [searchByTitle, setSearchByTitle] = useState('');
  • cambiar un poco el useEffect:
useEffect(() => {
	if (searchByTitle.length > 0) {
		setFilteredProducts(filterProductsByTitle(products, searchByTitle));
	} else {
		setFilteredProducts(products);
	}
}, [products, searchByTitle]);
  • Cambiar el producto en el .map() del home:
<div className='grid grid-cols-4 w-full max-w-screen-lg'>
	{filteredProducts.map((product) => (
		<Card key={product.id} item={product} />
	))}
	<ProductDetail />
</div>

Creo que el arreglo original de items no debería incluirse en el estado. Yo debería tener una constante que almacene ese arreglo después de hacer un llamado a la función que hace el fetch, y hacerle un freeze() para volverlo inmutable. No debería ponerlo en el estado porque eso lo hace susceptible de ser modificado así sea por error. Ese arreglo nunca debe cambiar. El filtered en cambio si debe estar en el estado, se espera que cambie cada vez que cambia el contenido del input. Lo que esperamos que no cambie nunca no debe ponerse en el estado. Cuando el input esté vacío, se puede setear el filtered con un spread (copia) del arreglo de items original inmutable para que haga re-render. No es algo crítico, pero ayuda a evitar errores.

Aqui si que me enrede un poquito 😦

Buenas a todos! Yo lo hice un poco distinto. Acepto criticas constructivas:
*Home/index.jsx:
//Agregue property value en el imput:

        <input 
          type="text" 
          placeholder='Search a product'
          className='rounded-lg border border-black p-4 mb-4 w-3/4 focus:outline-none'
          value={context.searchByTitle}
          onChange={(event) => context.setSearchByTitle(event.target.value) } />
      
      <div className='grid gap-1 grid-cols-4 w-full max-w-screen-lg'>
        { 
            context.filteredItemsByTitle?.map(item => (
              <Card key={item.id} data={item} />
            ))
        }
      </div>

En Context/index.jsx:
//No agrego el estado filteredItems, solo hago una funcion filteredItemsByTitle y luego lo pase en el provider:

    const filteredItemsByTitle = items?.filter(
        (item) => {
            const itemTitle = item.title.toLowerCase();
            const searchTitle = searchByTitle.toLowerCase();
            return itemTitle.includes(searchTitle);
        }
    );

Yo lo hice directamente con el render principal, ya que todos los productos en su titulo tienen un espacio:

{loading && products.map(product=>{
        if(product.title.toLowerCase().includes(searchValue.toLowerCase())){
          
          return<Card 
          key={product.id}
          name={product.title}
          category={product.category.name}
          img={product.images[0]}
          price={product.price}
          description={product.description}
          id={product.id}
          />
        }
      })}

Por aquí dejo un hook useDebounce para que no se haga la búsqueda cada vez que se escriba.

import { useEffect, useState } from 'react';

export const useDebounce = (value: string, delay: number) => {
  const [debounceValue, setDebounceValue] = useState('');
  useEffect(() => {
    const timeoutId = setTimeout(() => setDebounceValue(value), delay);

    return () => {
      clearTimeout(timeoutId);
    };
  }, [value]);

  return debounceValue;
};
Es costoso, a nivel de rendimiento de la app, filtrar por cada tecla que presiona un user. Por eso creé un customHook para esperar a que el cliente termine de teclear la palabra que desea buscar y una vez transcurrido un tiempo específico se ejecute el filtrado ```js import { useState, useEffect } from 'react'; export const useDebounce = (value: string, delay: number) => { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; }; ```Se ejecuta en mi context: ```js const debouncedSearch = useDebounce(search, 500); useEffect(() => { if (debouncedSearch.length > 0) { setFilteredItems(items.filter(item => item.title.toLowerCase().includes(debouncedSearch.toLowerCase()))); } else { setFilteredItems(items); } }, [debouncedSearch, items]) ```
Siento que se complicó un poco con el filtrado. Pero como dicen todos, en la programación siempre hay manera diferentes de hacerlo. Lo que hice fue crear una constante en el context, que me filtrara apartir del valor de "searchBytitle". Y esa constante, es la que retorno en el provider del context para que se utilizada en la home:![](https://static.platzi.com/media/user_upload/image-defb2aa5-d77c-4785-a155-fe776b7b0421.jpg) Además, amplié un poco la búsqueda, para que la hiciera por description y category también. ![](https://static.platzi.com/media/user_upload/image-300e4b1c-bf0f-4554-8ec9-d7b93e4fbb4e.jpg) Con eso aprovecho la virtud del .filter, de que si el valor que recibe es "null", vacio o "undefined" me retorna el array completo.
Reto Completado![](https://static.platzi.com/media/user_upload/image-ea7f5714-e31f-4cb5-a53e-8529bc9679cd.jpg)![](https://static.platzi.com/media/user_upload/image-d950357d-0fa2-4228-b04e-b8279ceefb07.jpg)![](https://static.platzi.com/media/user_upload/image-85997363-7f41-450d-bae4-ad579effb12e.jpg)![](https://static.platzi.com/media/user_upload/image-87143873-bb73-4b0d-a92a-3c389b66b523.jpg)![](https://static.platzi.com/media/user_upload/image-1d7f4a5f-2872-4265-a221-2e8e2bc4528a.jpg)![](https://static.platzi.com/media/user_upload/image-7cc69813-144d-4cb1-ab09-4e72ac00a1f7.jpg)

Yo me llevé el div a renderView asi se me veia todo completo y las columnas aparecen cuando sea necesario. Me quedaba el “no hay resultados” en una sola columna de las 4

const renderView = () => {
    if (context.searchValue?.length > 0) {
      if (context.filteredItems?.length > 0) {
        return (
          <div className='flex pb-10 flex-col md:grid md:grid-cols-3 lg:grid lg:grid-cols-4 mx-auto items-center w-4/5 justify-center gap-4 md:gap-6  lg:gap-10'>
            {context.filteredItems?.map((item) => {
              return <Card key={item.id} {...item} />;
            })}
          </div>

        )
      } else {
        return (
          <div className='flex flex-col justify-center items-center space-x-2 mx-auto py-28'>
            <img className='w-28 ' src="imagen" alt="no results" />
            <h2 className='text-center text-3xl opacity-60 py-6'>No Result Found</h2>
            <p className='text-center text-xl opacity-60'>We can't find any item matching your search</p>
          </div>
        )
      }

    } else {
      return (
        <div className='flex pb-10 flex-col md:grid md:grid-cols-3 lg:grid lg:grid-cols-4 mx-auto items-center w-4/5 justify-center gap-4 md:gap-6  lg:gap-10'>
          {context.items?.map((item) => {
            return <Card key={item.id} {...item} />;
          })}
        </div>

      )
    }
  }

A mi se me ocurrio poner un else que llene los items en filtered items para dejarlo a como funciona por default

  useEffect(() => {
    if (searchByTitle) {
      setFilteredItems(filteredItemsByTitle(items, searchByTitle));
    } else {
      setFilteredItems(items);
    }
  }, [items, searchByTitle]);
### El API de Platzi esta algo Creepy! ![](https://static.platzi.com/media/user_upload/image-09f77c86-b130-4d26-9b04-507928eb45a4.jpg)

Si le ponen un <div></div> y un <div></div> después del elemento de “We don’t have anything to show” el texto se centra. Sólo así conseguí centrarlo, aunque no en todos los tamaños.

## ✨🦄 Otra forma de renderizar los productos es declarar un estado local que determinará cual es la colección de datos que se van a renderizar. ![](https://static.platzi.com/media/user_upload/image-e7233158-5977-491a-b5e9-ecbcadd84e71.jpg) Esto reduce mucho el código que usamos para renderizar las cards. ![](https://static.platzi.com/media/user_upload/image-0f9a1315-8875-4f31-aaf0-6005a605a219.jpg)
Hello, En mi caso, para cuando no haya nada en la barra de busquedas, lo que he procedido a hacer es colocar, dentro del segundo useEffect que hemos creado una alternativa para que se llene el filtedItems, con los items cuando no haya razon de filtrado: ``` useEffect(()=>{ if(searchByTitle){ setFilteredItems(filteredItemsByTitle(items, searchBTitle)) }else if(filtedItems.length <= 0){ setFIlteredItems(items) } },\[items, searchByTitle]) ```
Aquí una version simplista de poner los items filtrados los originales: ![](https://static.platzi.com/media/user_upload/image-5030d5e8-a12d-4292-b706-3f80162e1333.jpg)
Demasiados if en ese render view. Preferi usar una estrategia diferente por si a alguien le interesa. Use un estado local para manejar los productos que muestro en pantalla y lo actualizo con la funcion de filtrado local que a su vez llama a la que esta en el context e igual uso un estado allProducts que vive en el context y que se alimenta de la api.![](https://static.platzi.com/media/user_upload/image-df6e57bc-63d5-4429-b454-3ec60a85a623.jpg) Y luego abajo evaluo si ese estado no tiene productos para mostrar el mensaje de sin resultados.![](https://static.platzi.com/media/user_upload/image-0f58507f-1780-4a6f-88d5-34890bb40104.jpg)
Yo lo hice un poco diferente en el componente Home. Primero desestructuré los estados que necesitaba desde el contexto: ```js const { items, searchByTitle, filteredItems } = context; ``` Y luego rendericé los elementos, validando con operadores ternarios, así no fue necesario definir la función de renderView(): ```js
{searchByTitle?.length > 0 ? ( filteredItems?.length > 0 ? ( filteredItems.map((item) => <Card key={item.id} data={item} />) ) : (
{"We don't have anything"}
) ) : ( items?.map((item) => <Card key={item.id} data={item} />) )}
```
Yo rendericé un poco diferente en el componente \
        {searchByTitle?.length > 0 ? (          filteredItems?.length > 0 ? (            filteredItems.map((*item*) *=>* <*Card* key={*item*.id} data={*item*} />)          ) : (            \
{"We don't have anything"}\
          )        ) : (          items?.map((*item*) *=>* <*Card* key={*item*.id} data={*item*} />)        )}      \
*Home,* lo que hice fue desestructurar la data del contexto: ```js const { items, searchByTitle, filteredItems } = context; ```Y luego con operadores ternarios hacer la renderización, sin necesidad de usar los if, ni la función renderView(): ```js
{searchByTitle?.length > 0 ? ( filteredItems?.length > 0 ? ( filteredItems.map((item) => <Card key={item.id} data={item} />) ) : (
{"We don't have anything"}
) ) : ( items?.map((item) => <Card key={item.id} data={item} />) )}
```
Para evitar duplicar la lógica para renderizar items o filteredItems agregué una constante en el componente ```js const itemsToRender = search.length>0 ? filteredItems : items; ```
En mi caso, parametricé la función para poder filtrar por el campo necesario. Si el filtrado viene del input,, busca en categoría, nombre y descripción ```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)); } ```
![](https://static.platzi.com/media/user_upload/image-71d86c7a-2b3d-42a6-90c5-c630a456ae04.jpg)![]()
![](https://static.platzi.com/media/user_upload/image-f4069314-f297-4526-9554-54bad8291b84.jpg)
que raro la API a veces tiene resultados muy diferentes a sus títulos jeje ![]() ![](https://static.platzi.com/media/user_upload/xd-8c73fced-95d4-47df-921c-adbe2a43e85d.jpg)
Yo lo hice de la siguiente manera: ```js // Search Products by title const [ searchValue, setSearchValue ] = useState(''); const onSearchValue = e => { setSearchValue(e.target.value) } let searchedProducts = [] if (searchValue) { searchedProducts = products.filter(product => product.title.toLowerCase().includes(searchValue.toLowerCase())) } else { searchedProducts = products; } ```
```js ////en contexto const [searchValue, setSearchValue] = useState(''); ////en home function Home() { const context = useContext(ContextShoppingCart); const filteredItems = context.items.filter((item)=> item.title.toLowerCase().includes(context.searchValue.toLowerCase())); return ( <Layout>
<input onChange={(e)=>context.setSearchValue(e.target.value)} value={context.searchValue}placeholder="Search" className="text-center border border-black rounded"></input>
{ ///si no hay nada en el input, filteredItems va tener todos los items filteredItems?.map(item =>( <Card key={item.id} item={item}/> )) }
<ProductDetail /> </Layout> ) } ```const \[searchValue, setSearchValue] = useState(''); const filteredItems = context.items.filter((item)=> item.title.toLowerCase().includes(context.searchValue.toLowerCase()));  filteredItems?.map(item =>(            \<Card key={item.id} item={item}/>          ))

Podemos utilizar directamente filteredItems:

const [items, setItems] = useState([]);
  const [filteredItems, setFilteredItems] = useState([]);
  const [searchValue, setSearchValue] = useState("");

  const filteredItemsByTitle = (items, searchByTitle) => {
    return items?.filter(item => item.title.toLowerCase().includes(searchByTitle.toLowerCase()));
  }

  useEffect(() => {
    setFilteredItems(filteredItemsByTitle(items, searchValue))
  }, [items, searchValue]);
function Home() {
  const { 
    setSearchValue,
    filteredItems
  } = useContext(ShoppingCartContext);

  return (
    <>
      <div className="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 => setSearchValue(event.target.value)}
      />
      <div className="grid gap-4 grid-cols-4 w-full max-w-screen-lg">
        {filteredItems?.map((item) => (
          <Card key={item.id} data={item} />
        ))}
        {filteredItems.length === 0 && <p>No products</p>}
      </div>
      <ProductDetail />
    </>
  );
}
```js
{(context.filteredItems || context.items)?.map((item) => ( <Card key={item.id} item={item} /> ))}
```

Mi solución sin usar if fue esta que opinan ?

Primero selecciona el array que quiero que usar la lista por defecto o la filtrada,

const itemsToRender = () => searchBar.length > 0 ? filteredItems : items;

Luego hice un render condicional para que me muestre el componente de vacio o las tarjetas de productos

        (itemsToRender() < 1 && searchBar.length > 0)
            ? <p>Not Found</p>
            : itemsToRender()?.map((item) => (<Card data={item} key={item.id} /> ))
       

y en el contex quedo de esta forma


  //filter items
  const [items, setItems] = useState(null)

  const [filteredItems, setfilteredItems] = useState([])

  const filteredItemsByTitle = (items, searchBar) => {
    return (items?.filter((item) =>
    item.title.toLocaleLowerCase().includes(searchBar.toLocaleLowerCase())
  ))
  };
  
  useEffect(() => {
  if (searchBar)setfilteredItems(filteredItemsByTitle(items, searchBar))
  }, [items, searchBar])
  

Reto completado ^^ pude filtrar dependiendo del Name, aunque no sé como mostrar mi resultado por aqui jajaja

Una mejora es usar el filtro de productos como un estado derivado, esto nos permite reducir la cantidad de renders al implementar un efecto adicional, además de usar el useMemo para memorizar el cálculo.

De esta forma, cada vez que se hace un cambio en el setsearchByTitle se realiza el filtro de los productos

  const productsFiltered = useMemo(
    () =>
      products.filter((product) =>
        product.title.toLowerCase().includes(searchByTitle.toLowerCase())
      ),
    [searchByTitle, products]
  );

Y se consume el estado filtrado en el Home.

function Home() {
  const { productsFiltered, error, setsearchByTitle, searchByTitle } =
    useContext(ShoppingCartContext);

  return (
    <Layout>
      <div className="flex  justify-center w-80 relative mb-4">
        <h1 className="font-medium text-xl">Home</h1>
      </div>
      <input
        type="text"
        placeholder="Search a product"
        className="rounded-lg border border-black p-2 w-80 mb-4 focus:outline-none"
        onChange={(event) => setsearchByTitle(event.target.value)}
        value={searchByTitle}
      />
      {error && <h3 className="font-medium text-red-600">{error}</h3>}
      <div className="grid gap-4 grid-cols-4 w-full max-w-screen-lg">
        {productsFiltered.map((product) => (
          <Card key={product.id} {...product} />
        ))}
      </div>
      <ProductDetail />
    </Layout>
  );
}

Esta es mi opción con todos los condicionales

import { Layout } from "../../Components/Layout"
import { useContext } from "react"
import { Card } from "../../Components/Card"
import { ProductDetail } from "../../Components/ProductDetail"
import { ShoppingCartContext, PropShoppingCart } from "../../Context"

const Home = () => {
  
  const context = useContext<PropShoppingCart | null>(ShoppingCartContext)
  const items = context?.items
  const filteredItems = context?.filteredItems

  return (
    <Layout>
      <div className="flex items-center justify-center relative w-80 mb-4">
       <h1 className="font-medium text-2xl">Exclusive Products</h1>
      </div>
      <input
      className="flex justify-center items-center border border-black rounded-lg w-80 mb-4 p-3"
      type="text"
      placeholder="Search a product"
      onChange={(event) => context?.setSearchByTitle(event.target.value) }
      />
      <div className="grid gap-4 grid-cols-4 w-full max-w-screen-lg">
        {!context?.searchByTitle? items?.map((item) => (
        <Card key={item.id} {...item}/>
      ) ):
      filteredItems && filteredItems?.length>0?filteredItems.map((item) => (
        <Card key={item.id} {...item}/>
      ) ):
      (<div className="flex justify-center items-center text-2xl">We don´t have this product</div>)
      }
      </div>
      <ProductDetail />
    </Layout>
  )
}

export {Home}

Esta es mi propuesta de código abreviado, usando typescript también

import { Layout } from "../../Components/Layout"
import { useContext } from "react"
import { Card } from "../../Components/Card"
import { ProductDetail } from "../../Components/ProductDetail"
import { ShoppingCartContext, PropShoppingCart } from "../../Context"

const Home = () => {
  
  const context = useContext<PropShoppingCart | null>(ShoppingCartContext)
  const filteredItems = context?.filteredItems

  return (
    <Layout>
      <div className="flex items-center justify-center relative w-80 mb-4">
       <h1 className="font-medium text-2xl">Exclusive Products</h1>
      </div>
      <input
      className="flex justify-center items-center border border-black rounded-lg w-80 mb-4 p-3"
      type="text"
      placeholder="Search a product"
      onChange={(event) => context?.setSearchByTitle(event.target.value) }
      />
      <div className="grid gap-4 grid-cols-4 w-full max-w-screen-lg">
      {filteredItems && filteredItems?.length>0?filteredItems?.map((item) => (
        <Card key={item.id} {...item}/>
      ) ):(<div className="flex justify-center items-center text-2xl">We don´t have this product</div>)}
      </div>
      <ProductDetail />
    </Layout>
  )
}

export {Home}

Dejando el código simple, se puede solo agregar un signo de interrogación para preguntar si existe un no los datos de filtrado y no hay que hacer ningun tipo de analisis, ya que cuando no se filtra nada, este por defecto muestra todos los productos

const Home = () => {
  
  const context = useContext<PropShoppingCart | null>(ShoppingCartContext)
  const filteredItems = context?.filteredItems

  return (
    <Layout>
      <div className="flex items-center justify-center relative w-80 mb-4">
       <h1 className="font-medium text-2xl">Exclusive Products</h1>
      </div>
      <input
      className="flex justify-center items-center border border-black rounded-lg w-80 mb-4 p-3"
      type="text"
      placeholder="Search a product"
      onChange={(event) => context?.setSearchByTitle(event.target.value) }
      />
      <div className="grid gap-4 grid-cols-4 w-full max-w-screen-lg">
      {filteredItems?.map((item) => (
        <Card key={item.id} {...item}/>
      ) )}
      </div>
      <ProductDetail />
    </Layout>
  )
}

export {Home}

Para el filtrado cree 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;

Acá lo que yo hice fue “saltarme” un poco la creación de los estados de filteredProducts, solo usé la parte de searchByTitle y en el mismo home hice el filtered.

const Home: FC = () => {
  const { items, searchByTitle, setsearchByTitle } =
    useContext(ShoppingCartContext);

  return (
    <Layout>
      <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={(e) => setsearchByTitle(e.target.value)}
      />
      <div className="grid gap-4 grid-cols-4 w-full max-w-screen-lg">
        {items
          .filter((item) =>
            item.title.toLowerCase().includes(searchByTitle.toLowerCase())
          )
          .map((item) => (
            <Card key={item.id} {...item} />
          ))}
      </div>
      <ProductDetail />
    </Layout>
  );
};