Skip to content

useEffect

⭐ Videos Premium ⭐

Esta sección es parte del curso en Udemy. Si quieres acceder a ella, puedes comprar el curso en Udemy: React + Firebase by bluuweb.

useEffect: El Hook de efecto te permite llevar a cabo efectos secundarios en componentes funcionales.

Al usar este Hook, le estamos indicando a React que el componente tiene que hacer algo después de renderizarse.

React recordará la función que le hemos pasado (nos referiremos a ella como nuestro “efecto”), y la llamará más tarde después de actualizar el DOM.

¿Se ejecuta useEffect después de cada renderizado? ¡Sí! Por defecto se ejecuta después del primer renderizado y después de cada actualización.

jsx
import { useEffect, useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("App mounted");
  });

  return (
    <>
      <h1>useEffect {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
};

export default App;

¿Por qué veo dos logs?

Si ves que se repiten tus logs, es porque está activado strict mode en React. En producción no deberías verlo.

más info stric mode

js
ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

¿Cómo podemos hacer que useEffect se ejecute solo una vez? Le pasamos un array vacío como segundo argumento.

jsx
useEffect(() => {
  console.log("App mounted");
}, []);

fetch

Una de las funciones más importantes de useEffect es la de hacer peticiones a una API.

jsx
import { useEffect, useState } from "react";

const App = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((response) => response.json())
      .then((data) => setData(data));
  }, []);

  return (
    <>
      <h1>useEffect</h1>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

async await

También podemos usar async await para hacer peticiones a una API. Pero para ello necesitamos una función async.

El problema:

jsx
useEffect(async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");

  const data = await response.json();
  setData(data);
}, []);

DANGER

Parece que escribiste useEffect(async () => ...) o devolviste una Promesa. En su lugar, escribe la función asíncrona dentro de tu efecto y llámala inmediatamente.

useEffect debe devolver una función de limpieza o nada.

El problema aquí es que se supone que el primer argumento de useEffect es una función que no devuelve nada (undefined) o una función (para limpiar los efectos secundarios). ¡Pero una función asíncrona devuelve una Promesa, que no se puede llamar como una función! Simplemente no es lo que el useEffect espera para su primer argumento.

más info

Solución:

jsx
import { useEffect, useState } from "react";

const App = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/users"
        );
        if (!response.ok) {
          throw "Error al conectar la API";
        }
        const data = await response.json();
        setData(data);
      } catch (error) {
        console.log(error);
        setData([]);
      }
    };
    fetchData();
  }, []);

  return (
    <>
      <h1>useEffect</h1>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

Extraer función

jsx
import { useEffect, useState } from "react";

const App = () => {
  const [data, setData] = useState([]);

  const fetchData = async () => {
    console.log("fetchData");
    try {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
      );
      if (!response.ok) {
        throw "Error al conectar la API";
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      console.log(error);
      setData([]);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <>
      <h1>useEffect</h1>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

WARNING

Nuevamente una advertencia: Cada vez que el componente se renderize se creará la función fetchData. Si no queremos que se cree cada vez, podemos usar useCallback.

Demostración:

jsx
import { useEffect, useState } from "react";

const App = () => {
  console.log("App");

  const [data, setData] = useState([]);
  const [counter, setCounter] = useState(0);

  const fetchData = async () => {
    console.log("fetchData");
    try {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
      );
      if (!response.ok) {
        throw "Error al conectar la API";
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      console.log(error);
      setData([]);
    }
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <>
      <h1>useEffect</h1>
      <button onClick={() => setCounter(counter + 1)}>
        Increment {counter}
      </button>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

useCallBack

¡No es necesario useCallback!

Estos ejemplos son para explicar el concepto, pero no es necesario usar useCallback en este caso., ya que no tiene un gran impacto en el rendimiento. Pero es bueno saberlo para cuando lo necesites.

Más info aquí

A futuro estoy casi seguro que eliminarán este Hook y React lo optimizará por nosotros.

Video demostrativo

useCallback es un hook que nos permite memorizar una función. Esto quiere decir que si la función que le pasamos como argumento no ha cambiado, useCallback no la volverá a crear.

Necesitas pasar dos cosas a useCallback:

  • Una definición de función que desea almacenar en caché entre renderizaciones.
  • Una lista de dependencias que incluye cada valor dentro de su componente que se usa dentro de su función.
jsx
import { useEffect, useState, useCallback } from "react";

const App = () => {
  const [data, setData] = useState([]);

  const fetchData = useCallback(async () => {
    console.log("fetchData");
    try {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
      );
      if (!response.ok) {
        throw "Error al conectar la API";
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      console.log(error);
      setData([]);
    }
  }, []);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  return (
    <>
      <h1>useEffect</h1>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

Otra solución:

jsx
import { useEffect, useState } from "react";

const fetchData = async (setData) => {
  console.log("fetchData");
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users");
    if (!response.ok) {
      throw "Error al conectar la API";
    }
    const data = await response.json();
    setData(data);
  } catch (error) {
    console.log(error);
    setData([]);
  }
};

const App = () => {
  console.log("App");

  const [data, setData] = useState([]);
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    fetchData(setData);
  }, []);

  return (
    <>
      <h1>useEffect</h1>
      <button onClick={() => setCounter(counter + 1)}>
        Increment {counter}
      </button>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

hook

js
import { useCallback, useEffect, useState } from "react";

export const useFetch = (url) => {
  console.log("useFetch");

  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const getData = useCallback(async () => {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error("Error fetching data");
      }
      const data = await response.json();
      setData(data);
    } catch (error) {
      setError(error.message);
    } finally {
      setLoading(false);
    }
  }, [url]);

  useEffect(() => {
    getData();
  }, [getData]);

  return { data, loading, error };
};

App.jsx

jsx
import { useState } from "react";
import { useFetch } from "./hooks/useFetch";

const App = () => {
  const [counter, setCounter] = useState(0);

  const { data, loading, error } = useFetch(
    "https://jsonplaceholder.typicode.com/users"
  );

  if (loading) {
    return <h1>Loading...</h1>;
  }

  if (error) {
    return <h1>{error}</h1>;
  }

  return (
    <>
      <h1>useEffect</h1>
      <button onClick={() => setCounter(counter + 1)}>
        Increment {counter}
      </button>
      <ul>
        {data.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
};

export default App;

Referencias

Próximamente