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 26

Preguntas 1

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

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>

Aqui si que me enrede un poquito 馃槮

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.

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);
        }
    );

Si le ponen un <div></div> y un <div></div> despu茅s del elemento de 鈥淲e don鈥檛 have anything to show鈥 el texto se centra. S贸lo as铆 consegu铆 centrarlo, aunque no en todos los tama帽os.

![](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; } ```
### El API de Platzi esta algo Creepy! ![](https://static.platzi.com/media/user_upload/image-09f77c86-b130-4d26-9b04-507928eb45a4.jpg)
```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} /> ))}
```

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]);

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])
  

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;
};

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;

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}
          />
        }
      })}

Ac谩 lo que yo hice fue 鈥渟altarme鈥 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>
  );
};