Bramucci Candela
Diego Portillo
Impecable
Andres Prieto
Acá te faltó un condicional para que te cargue la busqueda directamente cuando abres la URL con una busqueda, es solo un if:
  if (paramValue) {   setSearchValue(paramValue);   }
Sincronizar un input de búsqueda con la URL es una práctica común en aplicaciones como Platzi o YouTube, donde lo que escribes en el buscador queda reflejado en la dirección del navegador. En este reto vas a replicar ese comportamiento dentro de la aplicación de todos construida con React Router, conectando el estado del buscador con la URL para que la búsqueda sea compartible y persistente.
¿En qué consiste el reto de sincronizar búsqueda y URL?
La idea es que el texto escrito en el input de búsqueda se traslade automáticamente a la URL y, al mismo tiempo, que cualquier valor presente en la URL precargue el filtro del buscador al abrir la página.
En la aplicación ya tienes una barra de búsqueda que filtra los todos a partir del estado searchValue. Si escribes cortar, aparece solo el to do que coincide; si nada coincide, se muestra un mensaje de que no hay resultados. Lo que falta es que ese filtro viaje también por la URL.
¿Por qué llevar el valor del buscador a la URL? Para que la búsqueda sea compartible. Si copias y pegas la URL en otra pestaña, la app debe cargar el mismo filtro aplicado, igual que ocurre en Platzi o YouTube.
¿Cómo inspeccionar la URL actual con location.hash?
Para entender qué información viaja en la dirección, puedes apoyarte en el objeto location del navegador, que expone propiedades como location.href y location.hash [01:00].
Como el proyecto usa hash navigation, el dato relevante está en location.hash. Hoy, sin importar lo que escribas en el buscador, ese valor no cambia y siempre apunta al mismo home. Ese es justo el comportamiento que debes modificar.
location.hash o como parámetro de búsqueda.¿Cómo lograr la sincronización en ambos sentidos?
La sincronización debe funcionar en dos direcciones, y aquí viene lo interesante: el input alimenta a la URL, y la URL alimenta al input.
Del input hacia la URL
Cuando el usuario escribe en el buscador, además de actualizar el estado searchValue, debes actualizar la URL. Si escribes patito, la dirección debería reflejarlo, por ejemplo con un segmento como search=patito.
De la URL hacia el input
Si alguien abre una pestaña nueva con una URL que ya contiene un término de búsqueda, la app no debe responder con un not found. Debe renderizar el mismo home, pero con el filtro ya aplicado a partir de lo que indique la URL.
¿Qué hacer si la URL muestra not found al buscar? Significa que la ruta no está definida para aceptar parámetros dinámicos. Debes configurar la ruta para que lea el valor de búsqueda y lo pase como filtro inicial al
home.
¿Qué clases conviene repasar antes de resolverlo?
Este reto se apoya en conceptos que ya trabajaste antes, así que vale la pena volver sobre ellos si algo no fluye [02:30].
La diferencia es que aquí no vas a editar un to do a partir de su ID, sino a buscar todos a partir de un término que vive en la URL.
¿Qué es la hash navigation? Es una forma de manejar rutas en aplicaciones de una sola página usando el fragmento
#de la URL, evitando recargas y permitiendo que el cliente controle qué vista mostrar.
¿Cómo compartir tu solución en comentarios?
No importa si lo resuelves al primer intento o no, lo que importa es dar el 110 % de esfuerzo y documentar el proceso.
Los mejores comentarios son los que muestran el camino recorrido, no solo el resultado final. Cuéntame en los comentarios cómo conectaste el input con la URL y qué descubriste por el camino.
Bramucci Candela
Diego Portillo
Impecable
Andres Prieto
Acá te faltó un condicional para que te cargue la busqueda directamente cuando abres la URL con una busqueda, es solo un if:
  if (paramValue) {   setSearchValue(paramValue);   }
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 !useSearchParams
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:
Diego Garcia
Angel David Contreras Barrios
A caray!!
Enrique Aguilera
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
José Eduardo Vinagre de Dios
Neil Ruiz
Donovan RM
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 (
<div ``className``="cont">
<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>
</div> );}
export { TodoSearch };
Max Andy Diaz Neyra
Juan Mercado
Brianda Raquel Campoy Esquer
No quize aplicar eso en el TodoMachine pero lo implemente aqui https://briandacampoy.github.io/poke-api/#/
Mariana Contreras
Jhunior Eduardo Guevara Lázaro
Para que no navegue por cada valor tipeado en el input, agregué una funcion(handleKeyDown) al componente TodoSearch para que solo navegara hacia la ruta de busqueda cuando se apriete la tecla "enter".
const handleKeyDown = (event) => { if (event.key === "Enter") { // 👇 Get input value navigate(`/?search=${event.target.value}`); } }; // el input en el TodoSearch es: <input disabled={loading} // autoFocus className="TodoSearch" placeholder="cargando ..." onChange={onSearchValueChange} value={searchValue} onKeyDown={handleKeyDown} /> // fijarse el nuevo metodo onKeyDown que llama a la funcion handleKeyDown
Jairo Felipe Nieto Ruiz
https://nieto35.github.io/personal-page/#/api
Reto completado con Api de git Hub
Juan Castro
ProfesorYEEEEEESSSS ¡Muy bien hecho, Jairo! :clap:
Edward Junior Ramos VIllarreal
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.
Diana Florez
Me suena q es con useLocation 🤔
Gerardo J. Rito
La forma en que funciona el hook useSearchParams se me hace muy parecida a useState solo que este hook cambia la url no un estado en memoria y ese cambio provoca un re-render.
ANGEL DAVID SULBARAN CALDERON
Yo lo hice directamente en el TodoSearch, fue lo mas simple que pude ver sin mucho codigo
import { useSearchParams } from "react-router-dom"; import "./TodoSearch.css"; const TodoSearch = ({ searchValue, setSearchValue, loading }) => { const [searchParams, setSearchParams] = useSearchParams(); const onChange = (e) =>{ setSearchValue(e.target.value) setSearchParams({search: e.target.value}) } return ( <input type="text" placeholder="Buscar Todo" className="TodoSearch" value={searchValue || searchParams.get("search")} onChange={onChange} disabled={loading} /> ); }; export default TodoSearch;
Ricardo Daniel Quiroga
Matias SANCHEZ
alguien pudo realizarlo porque me rompi la cabeza pensando y no encontré una ?
David Espino
Te comparto mi solución compañero :) 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 😃
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" /> ) }
Mi solucion consiste en:
<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 };
const onSearchValueChange = (event) => { setSearchValue(event.target.value); navigate('?search=' + event.target.value); };
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 };
HomePage
import React from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useTodos } from '../useTodos'; import { TodoHeader } from '../../ui/TodoHeader'; import { TodoCounter } from '../../ui/TodoCounter'; import { TodoSearch } from '../../ui/TodoSearch'; import { TodoList } from '../../ui/TodoList'; import { TodoItem } from '../../ui/TodoItem'; import { TodosError } from '../../ui/TodosError'; import { TodosLoading } from '../../ui/TodosLoading'; import { EmptyTodos } from '../../ui/EmptyTodos'; import { CreateTodoButton } from '../../ui/CreateTodoButton'; import { ChangeAlert } from '../../ui/ChangeAlert'; function HomePage() { const [searchParams] = useSearchParams(); const searchText = searchParams.get('searchText'); const { state, stateUpdaters } = useTodos(); const navigate = useNavigate(); const { error, loading, searchedTodos, totalTodos, completedTodos, // openModal, searchValue, } = state; const { // setOpenModal, // addTodo, completeTodo, deleteTodo, setSearchValue, sincronizeTodos, } = stateUpdaters; if(searchText && searchText !== searchValue){ setSearchValue(searchText); } return ( <React.Fragment> <TodoHeader loading={loading}> <TodoCounter totalTodos={totalTodos} completedTodos={completedTodos} /> <TodoSearch searchValue={searchValue} setSearchValue={setSearchValue} /> </TodoHeader> <TodoList error={error} loading={loading} totalTodos={totalTodos} searchedTodos={searchedTodos} searchText={searchValue} onError={() => <TodosError />} onLoading={() => <TodosLoading />} onEmptyTodos={() => <EmptyTodos />} onEmptySearchResults={ (searchText) => <p>No hay resultados para {searchText}</p> } > {todo => ( <TodoItem key={todo.id} text={todo.text} completed={todo.completed} onComplete={() => completeTodo(todo.id)} onEdit={()=> navigate('/edit/' + todo.id, {state: { todo }})} onDelete={() => deleteTodo(todo.id)} /> )} </TodoList> {/* {!!openModal && ( <Modal> <TodoForm addTodo={addTodo} setOpenModal={setOpenModal} /> </Modal> )} */} <CreateTodoButton onClick = {()=> navigate('/new')} // setOpenModal={setOpenModal} /> <ChangeAlert sincronize={sincronizeTodos} /> </React.Fragment> ); } export { HomePage };
TodoSearch
import React from 'react'; import './TodoSearch.css'; import { useNavigate } from 'react-router-dom'; function TodoSearch({ searchValue, setSearchValue, loading }) { const navigate = useNavigate(); const onSearchValueChange = (event) => { setSearchValue(event.target.value); const params = new URLSearchParams({ searchText: event.target.value, }); navigate('/?'+params.toString()); }; return ( <input className="TodoSearch" placeholder="Cebolla" value={searchValue} onChange={onSearchValueChange} disabled={loading} /> ); } export { TodoSearch };
La solución que construí está basada en la utilización del hook useSearchParams y para implementarla sólo ajusté el componente HomaPage.jsx, por considerar que esto genera ventajas a nivel de separación de responsabilidades, escalabilidad y flujo claro:
import { useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useTodos } from '../useTodos'; import { TodoHeader } from '../../ui/TodoHeader'; import { TodoCounter } from '../../ui/TodoCounter'; import { TodoSearch } from '../../ui/TodoSearch'; import { TodoList } from '../../ui/TodoList'; import { TodoItem } from '../../ui/TodoItem'; import { TodosError } from '../../ui/TodosError'; import { TodosLoading } from '../../ui/TodosLoading'; import { EmptyTodos } from '../../ui/EmptyTodos'; import { CreateTodoButton } from '../../ui/CreateTodoButton'; import { ChangeAlert } from '../../ui/ChangeAlert'; function HomePage() { const navigate = useNavigate(); const { state, stateUpdaters } = useTodos(); const [searchParams, setSearchParams] = useSearchParams(); const searchParam = searchParams.get('search') || ''; const { error, loading, searchedTodos, totalTodos, completedTodos, searchValue, } = state; useEffect(() => { if (searchParam !== searchValue) { setSearchValue(searchParam); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParam]); const updateSearchValue = (value) => { if (value) { setSearchParams({ search: value }); } else { setSearchParams({}); } setSearchValue(value); }; const { completeTodo, deleteTodo, setSearchValue, sincronizeTodos, } = stateUpdaters; return ( <> <TodoHeader loading={loading}> <TodoCounter totalTodos={totalTodos} completedTodos={completedTodos} /> <TodoSearch searchValue={searchValue} setSearchValue={updateSearchValue} /> </TodoHeader> <TodoList error={error} loading={loading} totalTodos={totalTodos} searchedTodos={searchedTodos} searchText={searchValue} onError={() => <TodosError />} onLoading={() => <TodosLoading />} onEmptyTodos={() => <EmptyTodos />} onEmptySearchResults={ (searchText) => <p>No hay resultados para {searchText}</p> } > {todo => ( <TodoItem key={todo.id} text={todo.text} completed={todo.completed} onEdit={()=>navigate('/edit/' + todo.id,{state:{todo}})} onComplete={() => completeTodo(todo.id)} onDelete={() => deleteTodo(todo.id)} /> )} </TodoList> <CreateTodoButton onClick={()=>navigate('/new')} /> <ChangeAlert sincronize={sincronizeTodos} /> </> ); } export {HomePage};
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 }
Para resolver el reto tuve que hacer:
let location = useLocation(); let navigate = useNavigate();
React.useEffect(() => { if (searchValue.length > 0) { navigate({ pathname: location.pathname, search: `?search=${searchValue}`, }) } }, [searchValue])
let [searchParams, setSearchParams] = useSearchParams(); const wordFromPath = searchParams.getAll('search')[0];
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
Se puede hacer usando el hook useSearchParams escribi una implementación un poco diferente usando 2 UseEffect un para searchParams y otro para searchValue, para que permita actualizar en ambas direcciones ya sea se escriba en la URL o en el campo de busqueda, adicionalmente que el que escuche searchParams, tambien escuche loading.
function TodoSearch({ searchValue, setSearchValue, loading }) { const [searchParams, setSearchParams] = useSearchParams(); React.useEffect(() => { const searchText = searchParams.get("search") || null; if (searchText && searchText !== searchValue) { setSearchValue(searchText); } }, [loading, searchParams]); React.useEffect(() => { const searchText = searchParams.get("search") || null; if (searchText !== searchValue) { setSearchParams({ search: searchValue }); } }, [searchValue]); return ( <input placeholder="Search..." className="TodoSearch" value={searchValue} disabled={loading} onChange={(event) => { setSearchValue(event.target.value); }} /> ); }