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.
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.
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.
useEffect(() => {
console.log("App mounted");
}, []);
fetch
Una de las funciones más importantes de useEffect es la de hacer peticiones a una API.
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:
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.
Solución:
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
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:
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.
A futuro estoy casi seguro que eliminarán este Hook y React lo optimizará por nosotros.
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.
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:
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
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
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;