You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

1 Días
18 Hrs
3 Min
28 Seg

Reto: página de búsquedas con navegación

26/30
Resources

What is the challenge with React Router?

React Router is a powerful tool for handling navigation in React applications. In this class, we face an interesting challenge that consists of improving and customizing the search experience of an existing application. The goal is that whatever we type in the search bar is reflected directly in the URL, and that this change is also functional in reverse: that modifying the URL automatically updates the content. This type of functionality is critical to replicate intuitive and effective user experiences common on platforms such as Platzi and YouTube.

What is the current problem?

  1. Search filtering: Currently, the application allows you to filter items based on what you type in a search bar. For example, when typing "cut", only items matching that criteria appear.

  2. Static URL: The URL does not reflect the search criteria entered. No matter what you type in the search bar, the URL remains the same.

  3. URL-based search: If we try to access a URL that should display a result based on a search term (for example, "search=Duckling"), the page returns a status of "not found" or not found, instead of displaying the filtered items related to "Duckling".

How to fix this?

To address this challenge, the key is to make both the URL and the search bar dynamic. This involves the following:

  1. Update the URL when searching:

    • Update the URL to reflect the search term in the form of a hash or query string.
    • Use location.hash or location.search to construct the dynamic URL.
  2. Reading the search term from the URL:

    • When the application loads, it checks the URL to see if there is a search term present.
    • If there is, it automatically filters the results based on that term.
  3. Bi-directional synchronization:

    • Make sure that any changes in the search bar are reflected in the URL.
    • At the same time, if the URL changes externally (such as by pasting into another tab), make sure the interface is updated to show the correct results.

Final conclusion of the challenge

This exercise not only helps us polish React Router skills, but also invites us to think about how to make applications more accessible and easier to use. Remember, the key is to experiment and learn from the process. If you encounter difficulties, don't hesitate to share your attempts and discoveries, no matter if the solution is not perfect at the beginning - every attempt is another step towards mastering the tool!

Contributions 19

Questions 1

Sort by:

Want to see more contributions, questions and answers from the community?

Este reto es muy fácil con useSearchParams!! Qué lástima que no lo descubrí antes

import React from 'react'
import { useSearchParams } from 'react-router-dom'

export function TodoSearch({ setSearchValue, loading }) {
    const [searchParams, setSearchParams] = useSearchParams()
    const paramsValue = searchParams.get('search')

    const onSearchValueChange = ({ target: { value } }) => {
        setSearchValue(value)
        setSearchParams({ search: value })
    }

    return (
        <input
            className={`TodoSearch ${loading && 'TodoSearch--loading'}`}
            onChange={onSearchValueChange}
            value={paramsValue ?? ''}
            placeholder="Search a To-Do"
        />
    )
}

Para resolver el reto se puede ulitlizar el hook useSearchParams



En el HomePage.jsx agregamos:

 const [params, setParams] = useSearchParams();

Y se lo enviamos a TodoSearch:

<TodoSearch
          searchValue={searchValue}
          setSearchValue={setSearchValue}
          params={params}
          setParams={setParams}
        />

Ya en TodoSearch.jsx lo manejamos de la siguiente manera:

import React, { useEffect } from "react";

function TodoSearch({ searchValue, setSearchValue, params, setParams }) {
  const onSearchValueChange = (event) => {
    setSearchValue(event.target.value);

    let params = {
      search: event.target.value,
    };
    setParams(params);
  };

  useEffect(() => {
    const search = params.get("search") ?? "";
    setSearchValue(search);
  }, [params]);

  return (
    <input
      className="todo-search"
      placeholder="Search..."
      type="text"
      value={searchValue}
      onChange={onSearchValueChange}
    />
  );
}

export { TodoSearch };

De esa manera logramos el siguiente resultado

Uff, cuando te enfrentas a retos como este te es cuando realmente comprendes lo que creías entender de la clase.
Logré de resolverlo de la siguiente manera:

  • Dentro de App.js modifiqué la propiedad path para que aceptara el valor searchValue, también dejé el valor anterior para que la Route de HomePage siguiera funcionando aunque no se enviara el valor searchValue:
  • En TodoSearch.js utilicé useParams para cachar el valor searchValue de la url y se lo envie como value al input

    -Dentro de un useEffect() evalúo si params.searchValue existe, sí es así, actualizo el searchValue que recibimos desde props.
  • En la función onSearchValueChange agregue un navigate hacia el valor del input:

    No sé si es la mejor solución, pero funciona 😃

Mi solucion consiste en:

  1. Crear un ruta hija dinamica.
<Route path='/' element={<HomePage/>}>
    <Route path=':slug' element={<HomePage/>}/>
</Route>

Quedando asi App.js:

function App() {
  
  return (
    <HashRouter>
      <Routes>
        <Route path='/' element={<HomePage/>}>
          <Route path=':slug' element={<HomePage/>}/>
        </Route>
        <Route path='/new' element={<NewTodoPage/>}/>
        <Route path='/edit/:id' element={<EditTodoPage/>}/>
        <Route path='*' element={<p>No Found!</p>}/>
      </Routes>
    </HashRouter>
  );
}

export { App };
  1. Con el ayuda de useNavigate, creo la ruta con cada ingreso de teclado y navego a ella.
const onSearchValueChange = (event) => {
    setSearchValue(event.target.value);
    navigate('?search=' + event.target.value);
};
  1. Sí el usuario ingresa una url directa con su busqueda, me ayudo de una valorInput global y valido si hay una busqueda con ayuda de useLocation, espero a la carga del primer render y en 100ms realizo la busqueda.
let textSearchValue = searchValue;

if(location.search && !loading) {
    textSearchValue = location.search.replace('?search=', '');
    setTimeout(()=>setSearchValue(textSearchValue),50);
}

Quedando finalmente asi el archivo index.js de TodoSearch como sigue:

function TodoSearch({ searchValue, setSearchValue, loading }) {
  
  const navigate = useNavigate();
  const location = useLocation();
  let textSearchValue = searchValue;

  const onSearchValueChange = (event) => {
    setSearchValue(event.target.value);
    navigate('?search=' + event.target.value);
  };

  if(location.search && !loading) {
    textSearchValue = location.search.replace('?search=', '');
    setTimeout(()=>setSearchValue(textSearchValue),50);
  }

  return (
    <input
      className="TodoSearch"
      placeholder="Cebolla"
      value={textSearchValue}
      onChange={onSearchValueChange}
      disabled={loading}
    />
  );
}

export { TodoSearch };

A caray!!

yo utilice el hook useSearchParams para poder modificar los query params en la url, y también useEffect con un array vacio en el segundo argumento para que solo en el primer render se asigne el valor del searchParams al input

La hice de esta manera apoyandome de los compañeros: `import React from 'react';` `import { useSearchParams } from 'react-router-dom';` `import './TodoSearch.scss'` `function TodoSearch({``loading``, ``searchValue``, ``setSearchValue``}) {  ` `const [searchParams, setSearchParams] = useSearchParams();  ` `const paramsValue = searchParams.get('search');` `const updateSearchParams = (``value``) => {    setSearchParams({ search: ``value`` });  };  if (paramsValue) {    setSearchValue(paramsValue);  };` `  return (    ` `
      ` `<section ``className``="cont-box">        <input          ` `placeholder``="Busca tu tarea"                ` `value``={paramsValue ?? ''}          ``onChange``=` `{(``event``) => {            const value = ``event``.target.value;            setSearchValue(value);            updateSearchParams(value);          }}          ``disabled``={``loading``}        />` `...` `</section>    ` `
  );}` `export { TodoSearch };`

Aquí mi solución al reto utilizando useSearchParams en typescript y vite:

import { FC, useEffect, Dispatch, SetStateAction, ChangeEvent } from "react";
import './TodoSearch.css';
import { useSearchParams } from "react-router-dom";

interface Props {
    loading?:boolean,
    searchValue: string,
    setSearchValue: Dispatch<SetStateAction<string>>
}

const TodoSearch:FC<Props> = ({
    loading,
    searchValue,
    setSearchValue
}) => {

    const [searchParams, setSearchParams] = useSearchParams();

    
    useEffect(()=>{
        const paramsValue = searchParams.get('search') || '';
        setSearchValue(paramsValue)
    }, [searchParams])

    const onChangeHandler = (event:ChangeEvent<HTMLInputElement>):void => {
        setSearchValue(event.target.value);
        setSearchParams({ search : event.target.value });
    }

    return (
       <input 
        type="text" 
        className="TodoSearch" 
        placeholder="Ingresar nombre de tarea buscada" 
        value={searchValue}
        onChange={onChangeHandler}
        disabled={loading}
    />
    )
}

export { TodoSearch }

En este caso utilize el hook de react-router useSearchParams.
No es la mejor solucion pero aca la tratare de explicar:

Componente TodoSearch:

 const [searchParams, setSearchParams] = useSearchParams({});
let handleSearchChange = (e) => {
        setInputValue(e.target.value)
        setSearchParams({search: e.target.value})
    }

Poniendo dentro del evento change del input setSerachParams nos indica que va a agregar a la ruta una query llamada search que asignara el valor del input

En este punto tambien podemos añadir un evento de tipo enter asi cuando el usuario haga enter el hook useNavigate nos lleve a la ruta asignada.

En el hook useTodos donde tenemos el estado para controlar el inputValue en las validaciones les añadi unos nuevos condicionales… si… la solucion que se me vino es muy tediosa


    if(inputValue.length >= 1){
        arrayOfTodos = todos.filter(todo => todo.text.includes(inputValue))
    }else if(location.search === "?search="){
        console.log('nada que vuscar')
        navigate('/')
    }
    else if(location.search){
        let queryIndex = location.search.indexOf('=') + 1 
        let query = location.search.slice(queryIndex)
        
        setInputValue(query.split('%').map(text => {
            let indexText = text.indexOf('0') + 1
            let newText = text.slice(indexText)
            return newText
        }).join(' '))    
    }else{
        arrayOfTodos = todos
    }
  1. if: Alista en un nuevo array todas las todos que coincidan con lo que el usuario escriba en el input o quiera buscar.
  2. Si el usuario no nos manda nada entonces nos direcciona al home.
  3. Si el location.search existe o si el usuario esta buscando algo: implemente que formatee la query:
    en resumen ej:
    location.search = '/?search=ir%20con%20juandc’
    Lo que esta dentro de este if formatea ese string largo en solamente lo que escriba el usurio en el input y setea el valor del input a lo que nos llega como query
  4. por ultimo el else: nos dice que si no encontro ninguna todo con lo que busca, entonces las todos es a las todos por defecto

No quize aplicar eso en el TodoMachine pero lo implemente aqui
https://briandacampoy.github.io/poke-api/#/

Para resolver el reto tuve que hacer:

  1. Crear un effect para que escuhe cambios en searchValue y navegar al mismo location con un param search
  let location = useLocation();
  let navigate = useNavigate();
  React.useEffect(() => {
    if (searchValue.length > 0) {
      navigate({
        pathname: location.pathname,
        search: `?search=${searchValue}`,
      })
    }
  }, [searchValue])
  1. utilizo el hook useSearchParams de react-router-dom para traer ese valor cuando cambie la ruta
let [searchParams, setSearchParams] = useSearchParams();

const wordFromPath = searchParams.getAll('search')[0];
  1. verifico que el parametro search traiga algun valor y si esa asi hago la busqueda por ese valor y actualizo el valor de searchValue
if (!searchValue.length >= 1) { 

    if(wordFromPath) {
      searchedTodos = todos.filter(todo => {
        const todoText = todo.text.toLowerCase();
        const searchText = wordFromPath.toLowerCase();
        return todoText.includes(searchText);
      });
      setSearchValue(wordFromPath)
    }
    else {
      searchedTodos = todos;
    }

  }

la verdad no se si sea la opcion mas optima ya que por cada cambio de searchValue estoy navegando a la misma pagina, quisiera saber cual seria una mejor opcion

https://nieto35.github.io/personal-page/#/api

Reto completado con Api de git Hub

Se utilizaría la propiedad search del hook useLocation para capturar la query que se le pasa a la url. Teniendo eso se hace el filtrado y se muestra el valor en el input. Y para el otro caso, se utilizaría el useNavigate al hacer submit.

Me suena q es con useLocation 🤔

mi implementación en app.js `<Route path='/:searchValue' element={<HomePage />} />  ` en HomePage.js `const { searchValue: searchParam } = useParams();` `useEffect(() => {        ` `if(searchParam) {            setsearchValue(searchParam);        ` `}` `    }, [searchParam, setsearchValue]);` y en TodoSearch.js `const navigate = useNavigate();    const location = useLocation();` `    const handleSearchChange = (event) => {        const newSearchvalue = event.target.value;        setsearchValue(newSearchvalue);` ``        const newHash = newSearchvalue ? `/${newSearchvalue}` : '/';        if(window.location.hash !== newHash) {            navigate(newHash, { replace: true })        }    }        useEffect(() => {        const searchParam = location.hash.replace('#/', '');        setsearchValue(searchParam)    }, [location, setsearchValue]);``
En APP cree la ruta ```js <Route path="/search/:text" element={<HomePage />} /> ```\<Route path="/search/:text" element={\<HomePage />} /> Luego en TodoSearch/index.js const params = useParams();  const navigate = useNavigate();   const onSearchValueChange = (event) => {    console.log(event.target.value);    navigate( '/search/' + event.target.value);  };   if( params?.text )  {    setSearchValue( params.text );  } ```js const params = useParams(); const navigate = useNavigate(); const onSearchValueChange = (event) => { console.log(event.target.value); navigate( '/search/' + event.target.value); }; if( params?.text ) { setSearchValue( params.text ); } ``` Con esto ya queda con URL bonita, que cambia con el search

Reto: página de búsquedas con navegación

.
El reto es implementar la búsqueda de TODOs por la url; es decir, si mostramos el location.hash mientras escribimos en el buscador de TODOs la url nunca cambia, por lo que el reto consiste en que desde el mismo home #/ cambie la url dependiendo de lo que escriban los usuarios en la búsqueda.
.
En el caso de que no se en encuentren resultados de busqueda, no debería de salir Not Found, sino que debe mostrarse el mismo home pero ya con algo filtrado.
.

Solución 1

.
Para resolver el reto, en el componente TodoSearch vamos a importar los react hooks useNavigate y useSearchParams, estos se utilizan para navegar entre rutas y gestionar los parámetros de búsqueda, respectivamente.
.
Empezamos creando las constantes navigate y searchParams, este último es un elemento que nos permite recuperar el valor de algún parámetro de búsqueda de la url actual; en este caso, el parámetro search y lo guardamos en searchQuery.
.
Utilizamos un useEffect para verificar que si cuando existe searchQuery entonces tengamos que modificar el estado de searchValue por medio de setSearchValue para que tome el valor del searchQuery.
.
Finalmente, en la función onSearchValueChange además de cambiar el valor de searchValue cada vez que escribamos, también cambiaremos la url por navegación hacia la ruta home, pero con ?search=${event.target.value} al final de la misma por medio de la propiedad search del navigate.
.

import React, { useEffect } from "react";
import "./TodoSearch.css";
import { useNavigate, useSearchParams } from "react-router-dom";

function TodoSearch({ searchValue, setSearchValue, loading }) {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const searchQuery = searchParams.get("search");

  useEffect(() => {
    if (searchQuery) {
      setSearchValue(searchQuery);
    }
  }, [searchValue]);

  const onSearchValueChange = (event) => {
    setSearchValue(event.target.value);
    navigate({
      pathname: "/",
      search: `?search=${event.target.value}`,
      replace: true,
    });
  };

  return (
    <input
      className="TodoSearch"
      placeholder="Cebolla"
      value={searchValue}
      onChange={onSearchValueChange}
      disabled={loading}
    />
  );
}

export { TodoSearch };

Solución 2

.
Otra solución mejor, podría ya no usar useNavigate y netamente utilizar searchParams y setSearchParams de la siguiente manera.
.
A setSearchParams se le puede mandar un objeto con el parámetro de búsqueda y valor correspondiente.
.

import React, { useEffect } from "react";
import "./TodoSearch.css";
import { useSearchParams } from "react-router-dom";

function TodoSearch({ searchValue, setSearchValue, loading }) {
  const [searchParams, setSearchParams] = useSearchParams();
  const searchQuery = searchParams.get("search");

  useEffect(() => {
    if (searchQuery) {
      setSearchValue(searchQuery);
    }
  }, [searchValue]);

  const onSearchValueChange = ({ target: { value } }) => {
    setSearchValue(value);
    setSearchParams({ search: value });
  };

  return (
    <input
      className="TodoSearch"
      placeholder="Cebolla"
      value={searchValue}
      onChange={onSearchValueChange}
      disabled={loading}
    />
  );
}

export { TodoSearch };
A mi se me ocurrió hacer lo siguiente: ```js function TodoSearch({ searchValue, setSearchValue, loading }) { const {searchFilter} = useParams() const navigate = useNavigate() const onChangeSearchValue = (event) => { event.preventDefault() setSearchValue(event.target.value); navigate(`/${event.target.value}`) } if (!loading) { if (searchFilter && !searchValue) { setSearchValue(searchFilter) } } return ( <input placeholder="Hacer curso React.js" className="TodoSearch" value={searchValue} onChange={onChangeSearchValue} disabled={loading} /> ); } ```function TodoSearch({ searchValue, setSearchValue, loading }) { const {searchFilter} = useParams() const navigate = useNavigate() const onChangeSearchValue = (event) => { event.preventDefault() setSearchValue(event.target.value); navigate(`/${event.target.value}`) } if (!loading) { if (searchFilter && !searchValue) { setSearchValue(searchFilter) } } return ( \<input placeholder="Hacer curso React.js" className="TodoSearch" value={searchValue} onChange={onChangeSearchValue} disabled={loading} /> );}
XD Se les fue una letra demás en la url <https://platzi.com/new-home/clases/3468-react-router/51638-reto-pagina-de-busquedas-con-navegacieon/> **navegacieon**