Los cambios que hice para que funcionará con React router dom versión 5 fueron:
En el archivo App.js
En el archivo TodoForm/index.js
En el archivo HomePage.js
Fundamentos de navegación en la web
¿Cuándo necesitas React Router?
SSR vs. Single Page Applications
Versiones de React Router: ¿Por qué son tantas? ¿Cuál elegir?
Introducción a React Router DOM 6
Instalación de React Router DOM 6
BrowserRouter vs. HashRouter
Route: componentes de navegación
Link vs. NavLink
useParams: rutas dinámicas
useNavigate: historial de navegación
Outlet: nested routes
Fake authentication con React Router DOM 6
useAuth: login y logout
Menú con rutas públicas y privadas
Navigate y redirects: protegiendo rutas privadas
Roles y permisos
Reto: composición de componentes con navegación
Reto: UX de login y logout
Reto: roles complejos
React Router en TODO Machine
Integrando React Router a proyectos en React
Creando las rutas de TODO Machine
Botón de editar TODOs
Generador automático de IDs
Cambiando modales por navegación
Obtener y editar TODOs
useLocation: transferencia de datos por navegación
Deploy con React Router en GitHub Pages
Próximos pasos
Reto: página de búsquedas con navegación
Reto: TODO Machine con React Router DOM 5
Reto: PlatziMovies con React Router
Reto: crea tu propio React Router
Espera más cursos de React.js
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Juan David Castro Gallego
Aportes 18
Preguntas 1
Los cambios que hice para que funcionará con React router dom versión 5 fueron:
En el archivo App.js
En el archivo TodoForm/index.js
En el archivo HomePage.js
Hace poco me paso que estaba actualizando esta libreria a la v6 en un proyecto grande. Estaba haciendo todos los cambios hasta que me tope que en la ultima version habian eliminado un feature que estabamos usando para bloquear la navegacion.
Hablo de usePrompt y useBlocker.
Esto se usa si por ejemplo el usuario esta completando un formulario y antes de guardar los cambios quiere navegar a otra ruta. Nosotros le mostramos un modal de confirmacion antes de hacer la navegacion si tiene cambios. Entonces usamos ese feature. Pero en la version 6 no se puede (o no descubri como) hacerlo.
Aquí les dejo la documentación de react-router v5 😎👇
Hecho! Este me parece uno de los mejores retos del curso, sin él nunca me hubiera probado una versión anterior de nada.
.
La mayoría de cambios son superficiales, cambian las rutas y algunos hooks por otros. Me voy a explayar en el reto de los query params, que sí es más complicado.
.
Anteriormente, se podía usar el hook useSearchParams. Lamentablemente este no está en la V5. Lo que hice después de muuuucha investigación fue hacer un hook propio (fuertemente inspirado en respuestas de StackOverflow)
.
import { useMemo } from 'react'
import { useLocation } from 'react-router-dom'
export function useQuery(value) {
const { search } = useLocation()
const setQuery = useMemo(() => new URLSearchParams(search), [search])
const query = setQuery.get(value)
return [query, setQuery]
}
Luego lo implementé de la siguiente manera:
import React from 'react'
import { useEffect } from 'react'
import { useHistory } from 'react-router-dom'
import { useQuery } from '../hooks/useQuery'
export function TodoSearch({ setSearchValue, searchValue, loading }) {
const [searchQuery] = useQuery('search')
const history = useHistory()
const onSearchValueChange = ({ target: { value } }) => {
setSearchValue(value)
history.push({ search: `search=${value}` })
}
useEffect(() => {
if (searchQuery && searchQuery !== searchValue)
setSearchValue(searchQuery)
}, [searchQuery, setSearchValue, searchValue])
return (
<input
className={`TodoSearch ${loading && 'TodoSearch--loading'}`}
onChange={onSearchValueChange}
value={searchQuery ?? ''}
placeholder="Search a To-Do"
/>
)
}
Se siente tan fácil una vez terminado… no fue directo, pero fue muy entretenido y un gran desafío.
React Router V5 vs V6 dev.to
Documentación de React Router reactrouter.com
No fue tan sencillo como parece… Batalle mucho con el reto de TodoSearch.
Dejo el codigo por si a alguien le sirve de apoyo.
function TodoSearch({ searchValue, setSearchValue, loading }) {
const history = useHistory();
const onSearchValueChange = (event) => {
const value = event.target.value;
setSearchValue(value);
history.push({ search: value });
//modificar la url y guarda todo en location
};
const param = history.location.search.slice(1);
// usar el metodo slice para quitar '?' al inicio del string
const searchParam = decodeURI(param) ?? searchValue;
// decodeURI es una funcion de javascript ayuda a decodificar la url.
return (
<input
className="TodoSearch"
placeholder="Cebolla"
value={searchParam}
onChange={onSearchValueChange}
disabled={loading}
/>
);
}
Lo único raro, fue switch en vez de routes y route.
.
El reto es replicar el mismo comportamiento de la aplicación con React Router DOM 5.
.
Para ello vamos a mantener la misma estructura de carpetas que utilizamos para la aplicación con React Router DOM 6 pero modificando algunos archivos donde algunos hooks o componentes proporcionados por este no existan o funcionen distintamente en la versión 5.
.
Para este reto hemos utilizado la versión 5.3.4
de React Router DOM.
.
"react-router-dom": "^5.3.4"
.
En src/routes/App.js
ya no se usa el componente Routes
de la versión 6, sino que se usa Switch
. En lugar de element
para definir el componente que se debe renderizar ahora utilizamos component
.
.
Otra diferencia es que en la versión 6 element
acepta un elemento de React directamente como <HomePage />
, mientras que en la versión 5 component
solo acepta una referencia a un componente como HomePage
.
.
En la versión 6, es más directo usar el prop element={<p>Not Found</p>}
con JSX, mientras que en la versión 5, se utiliza una función que retorna JSX component={() => <p>Not Found</p>}
.
.
en la versión 6 Routes
entiende de forma implícita cuándo una ruta es exacta, no se usa exact
porque el enrutador lo maneja automáticamente, mientras que en la versión 5 usa exact
para asegurar que la ruta exacta se corresponda.
.
import React from "react";
import { HashRouter, Route, Switch } from "react-router-dom";
import { EditTodoPage } from "./edit/EditTodoPage";
import { HomePage } from "./home/HomePage";
import { NewTodoPage } from "./new/NewTodoPage";
function App() {
return (
<HashRouter>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/new" component={NewTodoPage} />
<Route path="/edit/:id" component={EditTodoPage} />
<Route path="*" component={() => <p>Not Found</p>} />
</Switch>
</HashRouter>
);
}
export { App };
.
En el componente HomePage
, vamos a remplazar useNavigate
por useHistory
para la navegación con React Router DOM 5. El hook useHistory
utiliza un método push
para navegar a una ruta.
.
Ambas versiones permiten pasar un estado a través de la navegación, en la versión 6 con navigate('/edit/' + todo.id, { state: { todo } })
y en la versión 5 con history.push('/edit/' + todo.id, { todo })
.
.
import React from "react";
import { useHistory } 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 history = useHistory();
const { state, stateUpdaters } = useTodos();
const {
error,
loading,
searchedTodos,
totalTodos,
completedTodos,
searchValue,
} = state;
const {
completeTodo,
deleteTodo,
setSearchValue,
sincronizeTodos,
} = stateUpdaters;
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}
onEdit={() => {
history.push("/edit/" + todo.id, { todo });
}}
onComplete={() => completeTodo(todo.id)}
onDelete={() => deleteTodo(todo.id)}
/>
)}
</TodoList>
<CreateTodoButton
onClick={() => history.push("/new")}
// setOpenModal={setOpenModal}
/>
<ChangeAlert sincronize={sincronizeTodos} />
</React.Fragment>
);
}
export { HomePage };
.
En TodoForm
es hacer lo mismo, remplazar useNavigate
por useHistory
.
.
import React from 'react';
import { useHistory } from 'react-router-dom';
import './TodoForm.css';
function TodoForm(props) {
const history = useHistory();
const [newTodoValue, setNewTodoValue] = React.useState(props.defaultTodoText || '');
const onChange = (event) => {
setNewTodoValue(event.target.value);
};
const onCancel = () => {
history.push('/');
};
const onSubmit = (event) => {
event.preventDefault();
props.submitEvent(newTodoValue);
history.push('/');
};
return (
<form onSubmit={onSubmit}>
<label>{props.label}</label>
<textarea
value={newTodoValue}
onChange={onChange}
placeholder="Cortar la cebolla oara el almuerzo"
/>
<div className="TodoForm-buttonContainer">
<button
type="button"
className="TodoForm-button TodoForm-button--cancel"
onClick={onCancel}
>
Cancelar
</button>
<button
type="submit"
className="TodoForm-button TodoForm-button--add"
>
{props.submitText}
</button>
</div>
</form>
);
}
export { TodoForm };
.
Finalmente, en TodoSearch
es donde se dio el mayor cambio, puesto que habíamos implementado búsqueda por navegación con useSearchParams
. Sin embargo, este hook no existe en la versión 5 de React Router DOM.
.
Lo que se hizo fue utilizar useHistory
y useLocation
para obtener la propiedad search
de useLocation
, luego cada que escribamos algo en el input de búsqueda vamos a navegar con useHistory
hacia el pathname /
con ?search=${event.target.value}
concatenado con lo que escribamos en dicho input.
.
Finalmente, en un useEffect
revisamos que si el search
empieza con ?search=
significa que hemos accedido directamente a la búsqueda por la url, así que recuperamos el valor de la búsqueda, lo decodificamos en caso de que la búsqueda contuviera espacios representados como %20
y cambiamos el estado de searchValue
mediante setSearchValue
.
.
De esta manera obtendremos la búsqueda por navegación tanto por el input de búsqueda, como desde la url.
.
import React, { useEffect } from "react";
import "./TodoSearch.css";
import { useHistory, useLocation } from "react-router-dom";
function TodoSearch({ searchValue, setSearchValue, loading }) {
const { search } = useLocation();
const history = useHistory();
const onSearchValueChange = (event) => {
setSearchValue(event.target.value);
history.push({
pathname: "/",
search: `?search=${event.target.value}`,
});
};
useEffect(() => {
if (search.startsWith("?search=")) {
setSearchValue(decodeURIComponent(search.substring(8)));
}
}, [searchValue]);
return (
<input
className="TodoSearch"
placeholder="Cebolla"
value={searchValue}
onChange={onSearchValueChange}
disabled={loading}
/>
);
}
export { TodoSearch };
Aquí les comparto mi repositorio en github en donde la rama main está con react router dom v6, mientras que la rama proyecto-react-router-dom-5, la aplicación está con react router dom v5.
Repositorio:
curso-react-router-proyecto2
tuve unos errores orribles, los cuales no pude salir, aquí les muestro cuales eran
Tenia todos los imports que se necesitaban pero me seguian pasando los errores, como por ejemplo el primero que me dice que en ese archivo index de el folder TodoForm no encuentra el useHistory donde si lo estoy importando
Oigaan.
Lo logre. Utilizando
Switch, useHistory y useLocation de la V5
Pero el input de search porque cuando quiero escribir me queda invalido hasta que hago click?
por cada letra que quiero colocar es un click que debo hacer al input
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?