No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Curso de Introducci贸n a React.js

Curso de Introducci贸n a React.js

Juan David Castro Gallego

Juan David Castro Gallego

Manejo de efectos

15/23
Recursos

El hook de efecto en react nos permite ejecutar un pedazo de c贸digo cada vez que necesitemos, a lo largo de la vida de nuestro componente, tambi茅n cuando se cumplan ciertas condiciones.

Algo curioso e importante de saber es que el c贸digo dentro de nuestro hook de efecto no se ejecuta como el resto del c贸digo, se ejecutar谩 inicialmente justo antes de hacer el renderizado del HTML que resulte de nuestro c贸digo de React.

Condiciones para nuestro hook de efecto

El hook de React, useEffect, puede recibir dos argumentos:

  1. Funci贸n que se ejecutar谩 en cada fase del ciclo de vida de nuestro componente.
  2. Las condiciones de cu谩ndo debe ejecutarse esta funci贸n dentro de un arreglo, cada que se actualice cualquier dato que le pasemos a este arreglo, se volver谩 a ejecutar nuestra funci贸n.
React.useEffect(funcion, [dato1, dato2, datoN])

Diferentes maneras de actualizar nuestros componentes

Existen tres diferentes maneras para aplicar el hook de efecto, todas funcionan diferente a la hora de re-renderizar nuestros componentes.

  1. Sin pasar un arreglo como segundo argumento: useEffect(funcion)
    Cuando no le pasamos un segundo argumento con las condiciones de cu谩ndo se debe re-ejecutar nuestra funci贸n, React tomar谩 como condiciones que se debe ejecutar nuestra funci贸n todas las veces que ocurra un re-renderizado, y tambi茅n cada vez que se actualice alguna prop (Si es que el componente recibe alguna).

  2. Pasando un arreglo vac铆o: useEffect(funcion, [])
    Cuando pasamos un arreglo vac铆o, le est谩s diciendo a React que no hay ninguna condici贸n para volver a ejecutar el c贸digo de nuestra funci贸n, entonces solamente se ejecutar谩 en el renderizado inicial.

  3. Pasando un arreglo con datos: useEffect(funcion, [val1, val2, valN])
    Cuando especificas las condiciones dentro del arreglo del segundo par谩metro, le est谩s diciendo a React que ejecute nuestra funci贸n en el renderizado inicial y tambi茅n cuando alg煤n dato del arreglo cambie.

Simulando una petici贸n a una API

Dentro de una aplicaci贸n web, al trabajar con APIs, existen muchos factores para determinar cu谩nto tardar谩 en cargar nuestra aplicaci贸n, como la velocidad de nuestro internet, la distancia del servidor, etc.

Al trabajar con APIs tambi茅n debemos tener en cuenta que puede tardar en cargar mucho nuestra aplicaci贸n, o incluso puede ocurrir alg煤n error, todo esto lo debemos de manejar para mantener a nuestro usuario informado.

El hook de efecto nos permite saber cuando ya renderiz贸 nuestra aplicaci贸n, as铆 podemos mostrar un mensaje de cargando o alguna animaci贸n en lo que se completa la petici贸n, tambi茅n con JavaScript podemos manejar los errores con try y catch, y haciendo uso del hook de estado podemos guardar si est谩 cargando o hubo alg煤n error.

App/index.js

import React from 'react';
import { AppUI } from './AppUI';

function useLocalStorage(itemName, initialValue) {
  // Creamos el estado inicial para nuestros errores y carga
  const [error, setError] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const [item, setItem] = React.useState(initialValue);
  
  React.useEffect(() => {
  // Simulamos un segundo de delay de carga 
    setTimeout(() => {
      // Manejamos la tarea dentro de un try/catch por si ocurre alg煤n error
      try {
        const localStorageItem = localStorage.getItem(itemName);
        let parsedItem;
        
        if (!localStorageItem) {
          localStorage.setItem(itemName, JSON.stringify(initialValue));
          parsedItem = initialValue;
        } else {
          parsedItem = JSON.parse(localStorageItem);
        }

        setItem(parsedItem);
      } catch(error) {
      // En caso de un error lo guardamos en el estado
        setError(error);
      } finally {
        // Tambi茅n podemos utilizar la 煤ltima parte del try/cath (finally) para terminar la carga
        setLoading(false);
      }
    }, 1000);
  });
  
  const saveItem = (newItem) => {
    // Manejamos la tarea dentro de un try/catch por si ocurre alg煤n error
    try {
      const stringifiedItem = JSON.stringify(newItem);
      localStorage.setItem(itemName, stringifiedItem);
      setItem(newItem);
    } catch(error) {
      // En caso de alg煤n error lo guardamos en el estado
      setError(error);
    }
  };

  // Para tener un mejor control de los datos retornados, podemos regresarlos dentro de un objeto
  return {
    item,
    saveItem,
    loading,
    error,
  };
}

function App() {
  // Desestructuramos los nuevos datos de nustro custom hook
  const {
    item: todos,
    saveItem: saveTodos,
    loading,
    error,
  } = useLocalStorage('TODOS_V1', []);
  const [searchValue, setSearchValue] = React.useState('');

  const completedTodos = todos.filter(todo => !!todo.completed).length;
  const totalTodos = todos.length;

  let searchedTodos = [];

  if (!searchValue.length >= 1) {
    searchedTodos = todos;
  } else {
    searchedTodos = todos.filter(todo => {
      const todoText = todo.text.toLowerCase();
      const searchText = searchValue.toLowerCase();
      return todoText.includes(searchText);
    });
  }

  const completeTodo = (text) => {
    const todoIndex = todos.findIndex(todo => todo.text === text);
    const newTodos = [...todos];
    newTodos[todoIndex].completed = true;
    saveTodos(newTodos);
  };

  const deleteTodo = (text) => {
    const todoIndex = todos.findIndex(todo => todo.text === text);
    const newTodos = [...todos];
    newTodos.splice(todoIndex, 1);
    saveTodos(newTodos);
  };

  return (
    {/* Pasamos los valores de loading y error */}
    <AppUI
      loading={loading}
      error={error}
      totalTodos={totalTodos}
      completedTodos={completedTodos}
      searchValue={searchValue}
      setSearchValue={setSearchValue}
      searchedTodos={searchedTodos}
      completeTodo={completeTodo}
      deleteTodo={deleteTodo}
    />
  );
}

export default App;

Una vez sabemos exactamente cu谩ndo una aplicaci贸n est谩 cargando y cu谩ndo ha ocurrido alg煤n error, podemos usar esta informaci贸n para mostrar algo al usuario.

App/AppUI.js

import React from 'react';
import { TodoCounter } from '../TodoCounter';
import { TodoSearch } from '../TodoSearch';
import { TodoList } from '../TodoList';
import { TodoItem } from '../TodoItem';
import { CreateTodoButton } from '../CreateTodoButton';

// Desescructuramos las nuesvas props
function AppUI({
  loading,
  error,
  totalTodos,
  completedTodos,
  searchValue,
  setSearchValue,
  searchedTodos,
  completeTodo,
  deleteTodo,
}) {
  return (
    <React.Fragment>
      <TodoCounter
        total={totalTodos}
        completed={completedTodos}
      />
      <TodoSearch
        searchValue={searchValue}
        setSearchValue={setSearchValue}
      />

      <TodoList>
        // Mostramos un mensaje en caso de que ocurra alg煤n error
        {error && <p>Desesp茅rate, hubo un error...</p>}
        // Mostramos un mensaje de cargando, cuando la aplicaci贸n est谩 cargando lo sdatos
        {loading && <p>Estamos cargando, no desesperes...</p>}
        // Si termin贸 de cargar y no existen TODOs, se muestra un mensaje para crear el primer TODO
        {(!loading && !searchedTodos.length) && <p>隆Crea tu primer TODO!</p>}
        
        {searchedTodos.map(todo => (
          <TodoItem
            key={todo.text}
            text={todo.text}
            completed={todo.completed}
            onComplete={() => completeTodo(todo.text)}
            onDelete={() => deleteTodo(todo.text)}
          />
        ))}
      </TodoList>

      <CreateTodoButton />
    </React.Fragment>
  );
}

export { AppUI };

Contribuci贸n creada por: Brandon Argel.

Aportes 44

Preguntas 12

Ordenar por:

Los aportes, preguntas y respuestas son vitales para aprender en comunidad. Reg铆strate o inicia sesi贸n para participar.

馃挕 Como aprendimos en el minuto 04:57, podemos enviarle un segundo argumento a la funci贸n useEffect para determinar cu谩ndo ejecutamos o no el c贸digo de nuestro efecto.
.
馃攤 Podemos enviar un array vac铆o para decirle a nuestro efecto solo se ejecute una vez, cuando reci茅n hacemos el primer render de nuestro componente.
.
馃憘 O tambi茅n podemos enviar un array con distintos elementos para decirle a nuestro efecto que no solo ejecute el efecto en el primer render, sino tambi茅n cuando haya cambios en esos elementos del array.
.
馃攣 Si no enviamos ning煤n array como segundo argumento de nuestro efecto, esta funci贸n se ejecutar谩 cada vez que nuestro componente haga render (es decir, cada vez que haya cambios en cualquiera de nuestros estados).
.
.
驴Cu谩l de estas opciones crees que debimos usar en nuestro efecto dentro de useLocalStorage?
.
Al menos por ahora, lo mejor habr铆a sido enviar un array vac铆o para que nuestro efecto solo se ejecute una vez, en el primer amado a nuestro custom hook / render de nuestro componente. 馃槍
.
Lastimosamente, olvid茅 escribir ese segundo par谩metro durante la clase. Esto hace que el c贸digo de mi efecto se ejecute cada vez que hay un cambio en el estado. Y como hacemos cambios a estado dentro del efecto, entonces el efecto se ejecutar谩 sin parar todo el tiempo que usemos la aplicaci贸n. 馃槺
.
Afortunadamente, como todo el c贸digo del useEffect est谩 envuelto en un setTimeout, cada ejecuci贸n del c贸digo de efecto tarda 1 segundo en volver a ejecutarse, por lo que la app no va a crashear. 馃槗
.
隆Este error podemos resolverlo en el siguiente curso de la saga! 馃檹
Incluso podemos profundizar much铆simo m谩s en este tipo de errores en un curso de optimizaci贸n, rendimiento y debugging con React.js. 馃挌
.
.
馃挭 Mientras tanto, s茅 mejor que yo. No olvides escribir el segundo par谩metro de tu efecto en useLocalStorage para que el c贸digo de tu efecto solo se ejecute una vez y no tengas problemas de rendimiento en tu versi贸n de TODO Machine.

userEffect: efectos que ocurren despu茅s del renderizado


.- Se llama 鈥渦serEffect鈥 o efecto secundario porque le estamos indicando a React que el componente tiene que hacer algo despu茅s de renderizarse.

.- Se llama dentro del componente, ya que nos permite acceder a la variable del estado.

.- Recibe dos par谩metros: El primero una funci贸n, el segundo un arreglo vac铆o o dependencias.

.- Se ejecuta despu茅s de renderizarse el estado.

La clase que me faltaba para mejorar y comprender mejor useEffect de React 馃檶 .

Por no saber mucho de este tema, y practicarlo lo suficiente, falle una prueba de reclutamiento T_T
Todo porque no me queria desplegar una app, por un error que me lanzaba useEffect, incluso cuando en mi entorno de desarrollo funcionaba bien T_T

Vue, te extra帽o.

Para el manejo de errores, y loading, me gusta usar solo un estado para ello, lo uso as铆 desde el curso de Richard鈥
驴Que opinan, les gusta?

//Lo inicio como un objeto con ambos estados, error y false
  const [dataStatus,setDataStatus] = useState({loading:true,error:false})

y lo edito de esta forma, para evitar la sobreescritura.
//Para editar solo el loading
 setDataStatus({...dataStatus,loading:false})
//Para editar solo el error
setDataStatus({...dataStatus,error:true})
//Para editar ambos  銉(鈥⑩库)銉
 setDataStatus({loading:false,error:false})

#nuncaparesdeaprender 馃槃

Les super recomiendo este video para reforzar a煤n m谩s el concepto de los efectos

useEffect explicado al detalle - Con 3 mini Apps

No te olvides que la manera de incorporar el conocimiento y asimilarlo en tu cabeza es mediante la pr谩ctica y reflexion de la misma, por eso esta recomendacion es en serio, me ha ayudado mucho.

Usando useEffect pude modificar el titulo de la p谩gina din谩micamente dependiendo de la cantidad de tareas pendientes.


En el componente TodoCounter:

React.useEffect(() => {
    total - completed > 0
      ? (document.title = `${total - completed} ${
          total - completed === 1 ? "tarea pendiente" : "tareas pendientes"
        }`)
      : (document.title = `No  tienes tareas pendientes`);
  }, [total, completed]);
}
export { TodoCounter };

14.-Manejo de efectos

.
TL;DR: Una pantalla de carga, y en el caso de un error, manejarlo. Al terminar la pantalla de carga mostrar ahora si la informaci贸n.
.
Los efectos son una herramienta, espec铆ficamente un react hook que nos permiten ejecutar cierta parte del c贸digo de nuestros componentes para que no se ejecuten cada vez que hacemos render de nuestro componente, sino, dependiendo de ciertas condiciones.
.
Vamos a simular que la persistencia de datos no es en local storage sino que es una API real. Nuestra app tiene que hacer un request al backend para traer de vuelta esa info, pero eso no pasa inmediatamente, depende del internet y de muchas otras cosas.
.
Se va a simular el tiempo de espera, por lo que las apps no se deben quedar en blanco mientras se hace ese request, los usuarios deben poder ver nuestra app en lo que termina de traer esa informaci贸n.
.
Necesitamos 3 estados de carga:
.

  • Se est谩 cargando la informaci贸n AKA 鈥淟oading鈥︹
  • Que tuvimos un error.
  • Todo est谩 ok:).
    .
    Para esto podemos usar el react hook llamado 鈥淩eact.useEffect鈥 que ejecuta el c贸digo que le enviemos no despu茅s de renderizar el componente sino justo antes, cuando react ya tiene todo preparado para renderizar. Si queremos ejecutarlo despu茅s de que se hizo el render usamos 鈥淩eact.useLayoutEffect鈥 (en caso de eventos, event listeners o cosas por el estilo).
    .
    Se ejecuta antes de hacer el render, de transformar react a html cuando ya react termino de hacer todos sus c谩lculos internos.
    .
    Si tenemos dos elementos un estado y su actualizador podemos enviar un array de dos posiciones, pero si tenemos m谩s estados en el hook es mejor mandar un objeto en lugar de un array.

Si le pasamos un return al useEffect va a ejecutar ese codigo cuando el componente se desmonte.

React.useLayoutEffect se renderiza justo despu茅s del componente en el navegador pero antes de 鈥減intar鈥 en pantalla.
.

Si te toco tomar el curso donde usaban clases para crear componentes, te dejo una info muy importante sobre useEffect para entenderlo mejor y como el profesor lo esta usando para actualizar estados, de igual forma les recomiendo darse una leida sobre el ciclo de vida de React, es m谩s f谩cil entenderlo as铆, la info est谩 en la p谩gina oficial de React:

Consejo

Si est谩s familiarizado con el ciclo de vida de las clases de React y sus m茅todos, el Hook useEffect equivale a componentDidMount, componentDidUpdate y componentWillUnmount combinados.

& Ampersand es la palabra usada para denominar al signo 芦&禄, que en ingl茅s equivale a la conjugaci贸n copulativa and. Hace varios siglos, 芦&禄 formaba parte del alfabeto en ingl茅s, pero se cree que su origen se remonta al siglo I a. C. y que fue obra del primer taqu铆grafo de la historia.

Me parece que est谩 mal la conceptualizaci贸n de useEffect y useLayoutEffect, en el minuto 1:58 al 2:40.

Deber铆a haber un bot贸n para darle like a la clase

Esta clase si hay q darle replay!!!

No se si luego se ve como funciona cuando existe un error, pero pueden probarlo colocando la sgt l铆nea dentro del try:

throw new Error()

y tambi茅n habr铆a que modificar la condici贸n del TodoList y que sea:

{(!props.loading && !props.todos.length && !props.error) && <p>Crea tu primer TODO!</p> }

Para que si hay un error ya no salga el 鈥淐rea tu primer TODO鈥濃 si alguien tiene una correcci贸n la escribe 馃槂

He organizado un poco el c贸digo para la actualizaci贸n del UI dependiendo de los valores de loading y error. Dado que me segu铆 apareciendo 鈥渃rea tu primer todo鈥 aun cuando ten铆a todos en mi pantalla. Dejo el c贸digo acontinuaci贸n 馃憞

{(error) ?
    <p>Lo sentimos, ha ocurrido un error, vuelve a recargar la p谩gina</p> :
    (loading) ?
    <p>Estamos cargando, pronto podr谩s comenzar</p> :
    (!searchedTodos.length) ?
    <p>隆Crea tu primer TODO!</p> :
    searchedTodos.map(todo => 
        <TodoItem key={todo.text} text={todo.text} completed={todo.completed} deleted={todo.deleted} color={todo.color} todoComplete={() => todoComplete(todo.text)} todoDelete={() => todoDelete(todo.text)} />
        )
} 

A帽adi una condicion mas (como se ve en el codigo) ya que si tenemos un error no debemos poder crear un TODO

{(!loading && !searchedTodos.length && !error) && <p>Crea tu primer TODO</p>}

Genial esta clase! Dejo un par de condiciones para el TodoList:

{(!loading && totalTodos>0 && !searchedTodos.length) && <p>No se encontr贸 nada con "{estadoBusquedaValue}"</p>}
{(!loading && !totalTodos) && <p>T铆rate un TODO</p>}

Yo entend铆 que el useEffect es el equivalente a lo que en Angular
serian los ciclos de vida de un componente. Es como una mezcla mas o menos entre OnInit y OnChange.

Para entender un poco mejor useContext =

function Main() {
  const value = 'Mi valor de contexto';
  return (
    <Context.Provider value={value}>
      <MiComponente />
    </Context.Provider>
  );
}
  • Para crear el contexto en React, son necesarios tres pasos fundamentales
  1. Crear el contexto. (Creaiting).
  2. Dar Providing al contexto.
  3. Consumir el contexto.
  1. Para crear el contexto = const Context = createContext(鈥楧efault Value鈥);
  1. Para consumir el contexto en cuesti贸n =
import { useContext } from 'react';
function MyComponent() {
  const value = useContext(Context);
  return <span>{value}</span>;
}

Fua鈥 aca fue donde mori. La verdad que fue muy rapido esta clase. A verlo muy paso a paso!.

creando los efectos en react

Hice una peque帽a modificaci贸n para que se muestren diferentes mensajes de error cuando s铆 hay todos pero la b煤squeda no arroja nada. No es lo mismo eso a que no existan todos. Espero le sirva a alguien.

{ error && <p>Hubo un error, favor de contactar al servicio de soporte de la NASA...</p> }
        { loading && <p>Cargando...</p> }
        { (!loading && !searchedTodos.length  && !todos.length ) && <p>Crea tu primer tarea.</p> }
        { (!loading && !searchedTodos.length && todos.length > 0 ) && <p>Tu b煤squeda no arroj贸 resultados.</p> }

Falto ponerle la condici贸n a la renderizaci贸n de nuestro TodoItem, para que se muestren despu茅s de la carga, si dejaron TODOs se habr谩n percatado.
As铆 lo deje yo:

<TodoList>
  {error && <p>Failed to load</p>}
  {loading && <p>Loading...</p>}
  {(!loading && !searchedTodos.length) && <p>Create a TODO!</p>}
  {(!loading && !error) &&
    searchedTodos.map(todo => (
      <TodoItem
        completed={todo.completed}
        key={todo.text}
        text={todo.text}
        onComplete={() => toggleCompleteTodo(todo.text)}
        onDelete={() => deleteTodo(todo.text)}
      />
    ))
  }
</TodoList>

Pueden checar lo del error con la siguiente linea:

				throw new Error("Oh no!");

Por ejemplo, yo modifiqu茅 un poquis el useEffect y me qued贸 as铆 y funciona excelente:

React.useEffect(() => {
		setTimeout(() => {
			try {
				throw new Error("Ooooooh noooo!");
				if (!localStorage.getItem(itemName)) {
					localStorage.setItem(itemName, JSON.stringify(initialValue));
				}
				let parsedItem = JSON.parse(localStorage.getItem(itemName));
				
				setItem(parsedItem);
			} catch (error) {
				setError(error);
			}
			setLoading(false);
		}, 2000);
	});

REMINDER:

Los hooks useEffect y useLayoutEffect hacen lo mismo, en lo 煤nico que difieren es a la hora que se ejecutan:

useEffect:

  1. El estado del componente cambia
  2. El componente se vuelve a renderizar
  3. El componente es mostrado en pantalla
  4. useEffect se ejecuta

useLayoutEffect:

  1. El estado del componente cambia
  2. El componente se vuelve a renderizar
  3. useLayoutEffect se ejecuta y React espera a que termine
  4. El componente es mostrado en pantalla

Pregunta de examen:
驴Cu谩ndo se ejecuta React.useEffect?

Explica bien el profe. Y si no sabes algun tema lo repasa con facilidad.

Castro david, de granda, sacha, el de tailwind, los mejores por ahora.

useEffect ejecuta el codigo justo antes de renderizar nuestro componente, cuando React ya tiene todo preparado para renderizar.
useLayoutEffect es para ejecutar el codigo despues de que se haya renderizado el componente en el navegador (se usa mayormente para eventos).

Muy buen curso, explicas bastante bien. Y como curso introductorio para Reaft est谩 genial, a mi me costo aprenderlo por mi cuenta, con este curso se me hubiese hecho m谩s sencillo. Enhorabuena 馃憦

Bueno鈥 creo que la cosa se va complicando, tuve que ver dos veces esta clase y un video de YouTube para sentirme seguro de continuar. 馃槸
Hasta ahora excelente el curso, como todos los de Juan David. 馃槃

驴O sea si uso un async await deberia ir dentro de use effect? O antes?

Codigo clave de la clase:

function useLocalStorage(itemName,initialValue) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false)
  const [items, setItems] = useState(initialValue);

  useEffect(()=>{
    setTimeout(()=>{
      try {
        const localStorageItems = localStorage.getItem(itemName);
        let parsedItems = JSON.parse(localStorageItems) || initialValue;
        saveItems(parsedItems)
        setLoading(false);
      } catch (err) {
        setError(true)
      }
    },1000)
  })

  const saveItems = (newTodos)=>{
    try {
      const stringifiedTodos = JSON.stringify(newTodos);
      localStorage.setItem(itemName,stringifiedTodos);
      setItems(newTodos);
    } catch (e) {
      setError(true)
    }
  }
  return {
    items,
    setItems,
    loading,
    error
  }
import React from "react";
import { TodoCounter } from "./TodoCounter";
import { TodoSearch } from "./TodoSearch";
import { TodoList } from "./TodoList";
import { TodoItem } from "./TodoItem";
import { CreateTodoButton } from "./CreateTodoButton";
//import './App.css';

// const defaultTodos=[
//   {text: "Cortar cebolla", completed: true},
//   {text: "ir al curso de react", completed: false},
//   {text: "picar cebolla", completed: true},
//   {text: "lalalal", completed: false}
// ]

//Aqui crearemos una configuracion personalizada de Hooks
function useLocalStorage(itemName, initialValue){
  const [error, setError] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const [item, setItem] = React.useState(initialValue);

  React.useEffect(()=>{
    try {
      setTimeout(() => {
        const localStorageItem = localStorage.getItem(itemName)
        let parsedItem;
        
        if(!localStorageItem){
          localStorage.setItem(itemName, JSON.stringify(initialValue))
          parsedItem = initialValue
        }else{
          parsedItem = JSON.parse(localStorageItem)
        }
        setItem(parsedItem);
        setLoading(false);
      }, 1000);
      
    } catch (error) {
      setError(error);
    }
  })

  const saveItem = (newItem) => {
    try {
      const stringifiedTodos = JSON.stringify(newItem);
      localStorage.setItem(itemName, stringifiedTodos);
      setItem(newItem);
    } catch (error) {
      setError(error);
    }
  };
  return {
    item, 
    saveItem,
    loading,
    error
  };
}

function App() {
  const {item: todos, 
    saveItem: saveTodos,
    loading,
    error}=useLocalStorage('TODOS_V1',[])
  const [searchValue, setSearchValue] = React.useState('')

  const completedTodo = todos.filter(todo=>todo.completed).length
  const totalTodos = todos.length

  const completeTodo = (text) => {
    const todoIndex = todos.findIndex(todo => todo.text === text);
    const newTodos = [...todos];
    newTodos[todoIndex].completed = true;
    saveTodos(newTodos);
  };

  const deleteTodo = (text) => {
    const todoIndex = todos.findIndex(todo => todo.text === text);
    const newTodos = [...todos];
    newTodos.splice(todoIndex, 1);
    saveTodos(newTodos);
  };

  let searchedTodo = []

  if (!searchValue.length >= 1){
    searchedTodo = todos
  }else{
    searchedTodo = todos.filter(todo=>{
      const todoText = todo.text.toLowerCase()
      const searchedText = searchValue.toLowerCase()
      return todoText.includes(searchedText)
    })
  }

  //aqui utilizaremos useEffect 
  console.log('antes del useEffect');
  React.useEffect(()=>{
    console.log('Use effect');
  },[totalTodos])
  console.log('despues del useEffect');


  return (
  <React.Fragment>
      <TodoCounter
        completed={completedTodo}
        total = {totalTodos}
      />
      <TodoSearch
        searchValue={searchValue}
        setSearchValue={setSearchValue}
      />      
      <TodoList>
        {error && <p>desesperate, hubo un error...</p>}
        {loading && <p>Estamos cargando, no desesperes...</p>}
        {(!loading && !searchedTodo.length) && <p>!Crea tu primer todo隆</p>}
        {searchedTodo.map(todo =>(
          <TodoItem 
          key={todo.text} 
          text={todo.text}
          completed={todo.completed}
          onComplete={()=>completeTodo(todo.text)}
          onDelete={()=>deleteTodo(todo.text)}
          />
        ))}
      </TodoList>
      <CreateTodoButton/>
  </React.Fragment>
  );
}

export default App;

AYUUUDAAAA! Tengo un problema 馃槬 Estoy utilizando dos veces el custom Hook useLocalStorage, pero me indica que no debo declararlo dos veces:

const {item: todos, saveItem: saveTodos, dataStatus} = useLocalStorage('TODOS_V1', demoDefaultTodos );
const {item: categories, saveItem: saveCategories, dataStatus} = useLocalStorage('CATEGORIES_V1', defaultCategories );

驴Qu茅 debo hacer para que funcione?

https://www.youtube.com/watch?v=7qnRm9F2x50 Si no les quedo claro como a mi les recomiendo este video

Pregunta de examen:
驴Cu谩ndo se ejecuta React.useLayoutEffect?

驴useEffect vendr铆a a ser el equivalente al m茅todo watch en VUE 2, cierto?

Entonces, esto mejora la UX porque podemos dar indicativo de que la petici贸n del usuario se est谩 trabajando en lugar de dejar pasmada la aplicaci贸n right?