No tienes acceso a esta clase

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

useLocation: transferencia de datos por navegación

24/30
Recursos

Aportes 15

Preguntas 2

Ordenar por:

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

Yo he encontrado un error.
.
Resulta que la aplicación cada vez que damos click en los botones de añadir un TODO o editar un TODO, hace uso del custom hook useTodos() y este a su vez hace uso del custom hook useLocalStorage() (y como sabemos este es síncrono, requiere de tiempo).
.
Entonces cada vez que accedemos a las rutas /new o /edit/ debemos esperar que carguen los todos del localStorage nuevamente porque los necesitamos para las funciones addTodo() y editTodo() y aquí es donde está el problema.
.
Si modificamos el setTimeout() de useLocalStorage() a 5 segundos por ejemplo y luego accedemos a /new o /edit y agregamos o editamos información antes de que pasen 5 segundos habrá problemas.

  • En el caso de addTodo() se sobrescribirá la información, porque la constante newTodos va a ser igual a un arreglo vacío.
  • En el caso de editTodo() se romperá la aplicación, porque la constante todoIndex va a ser -1.

.
Para solucionar esto lo que hice fue deshabilitar el <textarea> y el botón de Añadir/Editar usando el state loading de useTodos()

  • En newTodoPage() y EditTodoPage() agregue lo siguiente:
//use el loading y se lo pase al <TodoForm />
const { editTodo, getTodo, loading } = useTodos()

return (
	<TodoForm
		loading={loading}
		...
		...
)

  • En TodoForm() agregue lo siguiente:
function TodoForm(props) {
	...
	...

	return (
		...
		<textarea 
			disabled={props.loading}
			...
		/>
		<button
			disabled={props.loading}
		>
			{props.submitText}
		</button>
	)
}

Esta sería mi respuesta al retor, cualquier sugerencia estaré pendiente:

React router v6

React router v5

Previamente, había publicado un código que tenía dos errores:

  • al entrar directamente a una url, state (location) no existía y tiraba un error.
  • si el id del to-do no existía, saltaba un error

Ya corregí esos errores, dejo el nuevo código abajo.

App.js

<Route path="/edit/:id" element={<EditTodoPage />} />

EditTodoPage.js

export function EditTodoPage() {
    const { editToDo, loading, getToDo } = useToDos()
    const { state } = useLocation()
    let { id } = useParams()
    id = Number(id) //useParams converts number to text. Just in case, it is converted to number again

    let toDoText

    if (!state) {
        if (loading) return <p>loading</p>
        let toDo = getToDo(id)
        if (!toDo) return <NotFound />
        toDoText = toDo.text
    } else ({ toDoText } = state)

    return (
        <TodoForm
            submitEvent={newText => editToDo({ id, newText })}
            title="Edit To-Do"
            submitText="Edit"
            defaultText={toDoText}
        />
    )
}

Para resolver el reto del minuto 13:22, leí la documentación del hook useNavigate() de React Router, y noté que la función navigate() no sólo recibe una ruta, sino también un objeto de opciones:

Y entre las opciones recibe la propiedad state, entonces desde el HomePage, agregué el objeto de opciones con la propiedad state a la función navigate(), asignándole como valor al state el texto del TODO de cada TodoItem que está siendo mapeado por el TodoList.

Luego en la página EditTodoPage, por medio del hook useLocation(), le asigné el valor del state a la propiedad defaultTodoText del componente TodoForm.

Y listo, de esta forma podemos enviar directamente el texto de cada TODO al formulario TodoForm sin necesidad de esperar los segundos para que carge el localStorage.

Toca ver varias veces esta clase🧐🤪

Pensé que iba a poder con este reto, pero una vez más encontré varias formas de Como no hacerlo…XD.
Aprendí que useLocation es inmutable.

useLocation: transferencia de datos por navegación

.
Para concluir la migración de portales y modales a rutas, vamos a recuperar el texto del todo que estamos editando, para no tener que volverlo a escribir si fuera un pequeño cambio.
.
En el componente HomePage vamos a modificar nuestra render prop onEdit. En la navegación vamos a necesitar pasar la información del TODO que estamos modificando por medio de options, el cual vendría a ser un objeto que permite una propiedad state que nos sirve para mandar datos hacia una ruta, en este caso la ruta /edit/:id.
.

onEdit={() => {
              navigate(
                '/edit/' + todo.id,
                {
                  state: { todo }
                },
              );
            }}

.
En el componente EditTodoPage vamos a utilizar el react hook useLocation para recibir la información del TODO que se nos pasó por navegación en state.
.
Dentro de la ruta de editar TODOs tenemos 2 posibilidades para llegar a la ruta /edit/:id; es decir, por medio del botón de editar TODO o de forma directa a través de la url.
.
Sucede que al ingresar por la url, internamente se deben de cargar los TODOs antes de poder realizar cualquier acción. Este proceso se muestra dentro el código del react hook useLocalStorage, donde estamos simulado que estamos realizando peticiones a una API por medio de un setTimeout para obtener los TODOs.
.
Por esta razón, para evitar problemas de sobreescritura o similares, vamos a manejar el estado de loading que nos proporciona useTodos. Esto nos permite saber cuando terminamos de cargar los TODOs, por lo que tendremos una variable todoText que va a cambiar según la situación:
.

  • Si accedemos por medio del botón de editar algún TODO, significa que tendremos la información del mismo en location y podremos tomar el texto de location.state.todo.text.
    .
  • Si no tenemos la información en location, significa que estamos accediendo por url y que los TODOs aún deben cargar. Por lo que si loading es verdadero entonces renderizamos un mensaje de carga.
    .
  • Si loading es falso significa que los TODOs terminaron de cargar, por lo que podemos buscar el TODO por su ID por medio de la función getTodo, y luego recuperar su texto.

.
Cualquiera que sea el modo en que obtengamos el texto del TODO, vamos a pasarlo como prop defaultTodoText a FormTodo.
.

import React from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { TodoForm } from '../../ui/TodoForm';
import { useTodos } from '../useTodos';

function EditTodoPage() {
  const location = useLocation();
  const params = useParams();
  const id = Number(params.id);
  
  const { state, stateUpdaters } = useTodos();
  const { loading, getTodo } = state;
  const { editTodo } = stateUpdaters;

  let todoText;
  
  if (location.state?.todo) {
    todoText = location.state.todo.text;
  } else if (loading) {
    return <p>Cargando...</p>
  } else {
    const todo = getTodo(id);
    todoText = todo.text;
  }

  return (
    <TodoForm
      label="Edita tu TODO"
      defaultTodoText={todoText}
      submitText="Editar"
      submitEvent={(newText) => editTodo(id, newText)}
    />
  );
}

export { EditTodoPage };

.
En el react hook useTodos como se mencionó anteriormente se tiene a una función getTodo que recibe un id y busca el TODO entre todos los TODOs y lo retorna. A su vez, esta función se retorna dentro del objeto state.
.

...

function useTodos() {
	...
  const getTodo = (id) => {
    const todoIndex = todos.findIndex(todo => todo.id === id);
    return todos[todoIndex];
  }
  ...
  
  const state = {
	  ...
    getTodo,
  };
  ...

  return { state, stateUpdaters };
}
...

export { useTodos };

.
Finalmente, en TodoForm el estado incial de newTodoValue será props.defaultTodoText o un string vacío.
.

const [newTodoValue, setNewTodoValue] = React.useState(props.defaultTodoText || '');

Alguien más pensó que no tiene sentido pasar la información por el location.state si de igual forma hay que esperar a que se carguen los TODOs del local storage antes de poder darle click al botón de editar/añadir ?

Por ejemplo, si se demora 10 seg en cargar los TODOs y hacemos click en editar antes de que termine la carga, la aplicación se rompe, esto lo solucioné deshabilitando el botón de editar hasta que terminara la carga… pero esa solución no me convence ya que no hay ninguna ventaja en pasar los datos por el location.state

“si, cambio un poquito el navegador” jajajajaj

Mi solución

Mi solución fue pasar en el NavigateOptions.state el texto del TODO cuando se navega a la ruta ‘/edit/:id’ :

        {todo => (
          <TodoItem
            key={todo.id}
            text={todo.text}
            completed={todo.completed}
            onComplete={() => completeTodo(todo.id)}
            onDelete={() => deleteTodo(todo.id)}
            onEdit={() => navigate('/edit/' + todo.id, {state: {text: todo.text} })}
          />
        )}

Luego en EditTodoPage se accede a ese objeto state mediante useLocation():

function EditTodoPage(){

   const { stateUpdaters } = useTodos();
   const { editTodo } = stateUpdaters;
   const params  = useParams();
   const id = Number(params.id);
   const location = useLocation();

    return(
        <TodoForm 
            label="Edita tu Todo"
            submitText='Actualizar'
            submitEvent={(newText) => editTodo(id, newText)}
            initialText={location.state?.text}
        />
    );
}

Juan dice que esta clase fue díficil…
Para mí lo fue, pero por ese bendito loading que me hizo estar debuggeando un buen rato! 😵

Me estaba sucediendo un error, muestro screenshot:

.
Y sucedía porque me salía del NewPage hacia el HomePage sin que terminarán de cargar los todos, así que, cuando ya habían cargado y el estado loading cambia a true, el componente TodoForm ya está desmontado, y ocasiona el error.
.
La solución a esto es DETENER la carga de los todos al salirnos del NewPage (y EditPage también). Cómo hacemos eso?
.
En efecto, como nos dice el error, debemos cancelar esa carga de todos desde una Cleanup Function en nuestro useEffect que genera el setTimeout dentro de useLocalStorage. Esto es lo equivalente a un willUnmount del antiguo React.

React.useEffect(() => {
		const timeout = setTimeout(() => {
			try {
				const localStorageItem = localStorage.getItem(itemName);
				let parsedItem;

				if (!localStorageItem) {
					localStorage.setItem(itemName, JSON.stringify(initialValue));
					parsedItem = initialValue;
				} else {
					parsedItem = JSON.parse(localStorageItem);
				}

				onSuccess(parsedItem);
			} catch (error) {
				onError(error);
			}
		}, 3000);

		//CLEANUP FUNCTION
		return () => {
			clearInterval(timeout);
		};
	}, [sincronizedItem]);

Yo me propuse lograr la funcionalidad antes de ver el video de esta clase, les comparto mi solución:

  • Primero edité la propiedad path de la etiqueta Route que nos lleva al componente EditTodoPage para que aceptara un parámetro más:
  • Luego le agregé a la función navigate de onEdit el parámetro todo.text
  • Y por último definí ese texto como valor por defecto de newTodoValue en el componente TodoForm 😃

    Y listo 😃
**Mi solucion:** **<u>HomePage.js</u>** ![](https://static.platzi.com/media/user_upload/image-fcf32558-9113-4565-b2b8-54f96bba8393.jpg) ![]()**<u>EditTodoPage.js</u>** ![](https://static.platzi.com/media/user_upload/image-7d5f49df-bd8c-4c05-a022-6a2fc574b735.jpg)![]()