No tienes acceso a esta clase

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

Reto: TODO Machine con React Router DOM 5

27/30
Recursos

Aportes 18

Preguntas 1

Ordenar por:

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

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.

https://github.com/remix-run/react-router/issues/8139

Aquí les dejo la documentación de react-router v5 😎👇

https://v5.reactrouter.com/web/guides/quick-start 💚

muy válido para entender por qué no van a actualizar el curso práctico de React

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

useQuery.js

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:

TodoSearch.js

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.

Documentación de React Router DOM V5

Documentación aquí

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.

Reto: TODO Machine con React Router DOM 5

.
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

Hola bueno los compañeros ya compartieron como cambiar a la version 5 utilizando distintos valores como Swich y history. Yo vengo a compartir mi solución sobre el **search🥸**, ya que estuve como loco un día entero jajaj. Lo que hice fue crear la ruta dinámica con el **path="/search/:slug?"** que quiere decir que puede recibir un slug o no para que no se rompa todo. Despues en el evento del Input se debe encargar de actualizar el estado (que renderiza las tareas encontradas) y tambien actualizar la ruta de esta forma : {   history.push({   pathname:"/search", search:event.target.value,    }) //ruta setSearchValue(event.target.value)//estado } Porque este quilombo de pasar el estado de search? Para asi poder hacer una validación si el usuario entra con la url de una busqueda. Aclaro que envolví esta validación en un **Efectt** que su **ciclo de vida es de una ejecucion**, porque me pareció lo mas adecuado para un caso real... **La ventaja** es que no se renderice cada vez que el usuario escriba una letra, solo si el usuario entre por primera vez con esa busqueda realizada. Siento que no es óptimo que se ejecute imaginando que fuesen consultas a una API por ejemplo. La desventaja es que si decide navegar por la URL no funcionará al menos que refresque la pagina. Les dejo el código, si hay algun error háganmelo saber quizas hice cualquiera 😂. .; ```jsx //APP <Route exact path="/search/:slug?" > <HomePage /> </Route > //TodoSearch function ToDoSearch({ searchValue, setSearchValue, loading }) { const location = useLocation(); const history = useHistory(); React.useEffect(()=>{ if(location.search){ setSearchValue(location.search.replace("?","")) console.log("EJECUCION") } },[]) //aca podria poner que dependa de searchValue pero tendría un costo alto de rendimiento return ( <input placeholder="Busca Tareas" className="search-input" value={searchValue} onChange={(event) => { history.push({ pathname:"/search", search:event.target.value, }); setSearchValue(event.target.value) }} disabled={loading} /> ) } ```
Les dejo "ToDos" los cambios que hice por si alguien también quiere tirar la toalla, para que no la tire!!! Como yo casi quería dejar el curso en este punto y luego me acordé del tema de PASITO a PASITO, una cosa a la vez y tener tambien visión global de las cosas y que esto esta hecho por personassssssss. No robots despreciables sin compasión. Me asocie con San ChatGPT, me encomende a los dioses de la internet que siempre usa el profe y empecé a cambiar lo que fui identificando como necesario: Pasos para el cambio: Ejecutar: `npm uninstall react-router-dom` Ejecutar: `npm install react-router-dom@5` Reemplazar: `Routes` por `Switch` Reemplazar: `<Route path="/xxx" element={< xxx />} />` por `<Route path="/xxx" component={ xxx } />` Reemplazar: `<Route path="/xxx" element={<Navigate to="/xxx" />} /> `por `<Redirect from="/xxx" to="/xxx" />` Reemplazar: `const navigate = useNavigate()` por `c` `const history = useHistory();` Reemplazar: `import { Routes, Navigate, useNavigate } from 'react-router-dom';` por `import { Switch, Redirect, useHistory }  from 'react-router-dom';` Reemplazar: `navigate('/xxx');` por `history.push('/xxx');` Remplazar: `onEdit={() => navigate(/edit/${toDo.id}, {state: toDo.text })}` por ``onEdit={()=> history.push(/edit/${toDo.id}?text=${toDo.text}`)}`` En el componente hijo: Añadir : `import { useHistory } from "react-router-dom";` `const history = useHistory()` Los Remplazos que más me sacaron la piedrunchis..., el tiempo y mas me hicieron sufrir: (me ayudo chatGPT 😉) En el componente hijo de Editar: Reemplazar: `if (location.state) {` `previousText = location.state} `por `if (location.search) {` `const params = new URLSearchParams(location.search) previousText = params.get('text')}` Si después de todo esto sigue fallando, le mandas a chatGPT todo tu código y que te acabe de ajustar la solución. Hay que mandar homepage, las dos rutas de edit y add, la app.js y el código de useToDos por si acaso. Suerte!
Les dejo "ToDos" los cambios que hice por si alguien también quiere tirar la toalla, para que no la tire!!! Como yo casi quería dejar el curso en este punto y luego me acordé del tema de PASITO a PASITO, una cosa a la vez y tener tambien vision global de las cosas y que esto esta hecho por personassssssss. No robots despreciables sin compasion. Me asocie con San ChatGPT, me encomende a los dioses de la internet que siempre usa el profe y empecé a cambiar lo que fui identificando como necesario: Pasos para el cambio: Ejecutar: `npm uninstall react-router-dom` Ejecutar: `npm install react-router-dom@5` Reemplazar: `Routes` por `Switch` Reemplazar: <;Route path="/xxx" element={< xxx />} /> por \<Route path="/xxx" component={ xxx } /> Reemplazar: \<Route path="/xxx" element={\<Navigate to="/xxx" />} /> por \<Redirect from="/xxx" to="/xxx" /> Reemplazar: `const navigate = useNavigate()` por `const history = useHistory();` Reemplazar: `import { Routes, Navigate, useNavigate } from 'react-router-dom';` por `import { Switch, Redirect, useHistory }  from 'react-router-dom';` Reemplazar: `navigate('/xxx');` por `history.push('/xxx');` Remplazar: onEdit={() => navigate(`/edit/${toDo.id}`, {state:  toDo.text })} por `onEdit={()=> history.push(`/edit/${toDo.id}?text=${toDo.text}`)} En el componente hijo: Añadir : `import { useHistory } from "react-router-dom";` `const history = useHistory()` Los Remplazos que más me sacaron la piedrunchis..., el tiempo y mas me hicieron sufrir: (me ayudo chatGPT 😉) En el componente hijo de Editar: Reemplazar: if (location.state) { previousText = location.state} por if (location.search) { const params = new URLSearchParams(location.search) previousText = params.get('text')} Si después de todo esto sigue fallando, le mandas a chatGPT todo tu código y que te acabe de ajustar la solución. Hay que mandar homepage, las dos rutas de edit y add, la app.js y el código de useToDos por si acaso. Suerte!
Les dejo "ToDos" los cambios que hice por si alguien también quiere tirar la toalla, para que no la tire!!! Como yo casi quería dejar el curso en este punto y luego me acordé del tema de PASITO a PASITO, una cosa a la vez y tener tambien vision global de las cosas y que esto esta hecho por personassssssss. No robots despreciables sin compasion. Me asocie con San ChatGPT, me encomende a los dioses de la internet que siempre usa el profe y empecé a cambiar lo que fui identificando como necesario: Pasos para el cambio: Ejecutar: `npm uninstall react-router-dom` Ejecutar: `npm install react-router-dom@5` Reemplazar:       `Routes`   por `Switch` Reemplazar:       \<Route path="/xxx" element={< xxx />} />   por \<Route path="/xxx" component={ xxx } /> Reemplazar: \<Route path="/xxx" element={\<Navigate to="/xxx" />} />  por \<Redirect from="/xxx" to="/xxx" /> - Reemplazar:       `const navigate = useNavigate()`  por `const history = useHistory();` Reemplazar: `import { Routes, Navigate, useNavigate } from 'react-router-dom';`  por `import { Switch, Redirect, useHistory }  from 'react-router-dom';` Reemplazar: `navigate('/xxx');`  por `history.push('/xxx');` Remplazar: `onEdit={() => history.push(`/edit/${toDo.id}`, {state:  toDo.text })  por `onEdit={() => history.push(`/edit/${toDo.id}?text=${toDo.text}`)}  En el componente hijo:   Añadir : `import { useHistory } from "react-router-dom";`  `const history = useHistory()` Los Remplazos que más me sacaron la piedrunchis..., el tiempo y mas me hicieron sufrir: (me ayudo chatGPT 😉) En el componete hijo de Editar: Reemplazar: if (location.state) { previousText = location.state}  por if (location.search) { const params = new URLSearchParams(location.search)      previousText  = params.get('text')} Si despues de todo esto sigue fallando, le mandas a chatGPT todo tu codigo y que te acabe de ajustar la solucion. Hay que mandar homepage, las dos rutas de edit y add, la app.js y el codigo de useToDos por si acaso. Suerte!
Recuerdo la primera vez que me puse aprender React, en ese tiempo aún tenían una batalla campal por ver que router usar (Solo que yo no lo sabía), el router estaba, solo estaba; cuando buscabas información sobre el tema terminabas más cerca de la batalla campal que de la solución; ahora es la guerra por los routers está digamos que en relativa paz; este reto no es dificil, solo que volver a esos tiempos es \* Recuerdos de Vietnam \* recordar el como intentaba usar cosas de las que no sabía lo suficiente, que ahora veo y es más facil, este reto me recordó un montón esos días... Así que gracias por el ataque de nostalgia, realmente he aprendido mucho desde esos días

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