Actualmente me estoy enseñando a mí mismo React, y me he topado con el siguiente código recortado para un gancho personalizado que usa el método fetch() .
import { useState, useEffect, useCallback } from "react"; import axios from "axios"; function useFetch(query, page) { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [list, setList] = useState([]); const sendQuery = useCallback(async () => { try { await setLoading(true); await setError(false); const res = await axios.get(url); await setList((prev) => [...prev, ...res.data]; setLoading(false); } catch (err) { setError(err); } }, [query, page]); useEffect(() => { sendQuery(query); }, [query, sendQuery, page]); return { loading, error, list }; } export default useFetch; No puedo entender bien el uso de useCallback() en este contexto. Mis ganchos fetch() hasta ahora solo importaban useState() y useEffect() y luego llamaban useEffect() cada vez que cambiaba uno de los parámetros. ¿Alguien puede explicarme qué hace exactamente useCallback() y cuándo debo usarlo para obtener datos de una API?
Creo que he encontrado el problema: si tiene un componente y una función que maneja la entrada del usuario llamando a la API (usando el enlace) con el valor actual cada vez que cambia, esta función interna se vuelve a crear cada vez que el componente vuelve a crear. renderiza, lo que ocurre cada vez que cambia algo en su interior. Esto significa que cuando cambia cualquier otra cosa dentro del componente, se vuelve a llamar a la API.
Este problema se puede resolver envolviendo la llamada a la API en el useCallback() y pasándole el valor de entrada como una dependencia, de modo que la API solo se llame cuando la entrada cambie, y la última respuesta se memorice entre las nuevas representaciones de la componente.
He encontrado un video que es una gran explicación .
Según el documento oficial: https://reactjs.org/docs/hooks-reference.html#usecallback .
UseCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders
Entonces, en su caso, no está usando la versión memorizada para niños, pero usa useEffect , por lo que useEffect se activará cuando cambie la query o la page , para ser honesto, no tiene que ser así, para mí esto es un poco sobre-diseñado, también podría hacerlo algo como esto:
useEffect(async () => { try { await setLoading(true); await setError(false); const re s = await axios.get(url); await setList((prev) => [...prev, ...res.data]; setLoading(false); } catch (err) { setErr or(err); } }, [query, page])por lo que se simplificará solo useEffect, no veo el punto de useCallback aquí.
useCallback memoriza la definición de la función, de modo que cada vez que un componente se vuelve a renderizar, no creará una nueva definición para esa función a menos que cambie la matriz de dependencia alimentada a useCallback .
Aquí, la función con useCallback se incluye en una matriz de dependencias para useEffect . Sin una memorización, cada vez que se llame a este enlace, creará una nueva definición de función para searchQuery y se volverá a representar, lo que dará como resultado un bucle infinito.
Para visualizar esto, he creado un ejemplo de codesandbox , abro la consola y veo que el componente que no usa useCallback procesa infinitamente.
-- Consulta.jsx
import useFetchWithCallback from "./useFetch"; import useFetchWithoutCallback from "./useFetchWithoutCallback"; const url = "https://jsonplaceholder.typicode.com/todos/1"; export const QueryWithCallback = () => { const { data } = useFetchWithCallback({ url }); console.log("Query with callback rerender"); return <h1>Query with callback: {data?.[0]?.id}</h1>; }; export const QueryWithoutCallback = () => { const { data } = useFetchWithoutCallback({ url }); console.log("Query without callback rerender"); return <h1>Query without callback: {data?.[0]?.id}</h1>; };-- useFetch.js
import { useState, useEffect, useCallback } from "react"; import axios from "axios"; function useFetch({ query, page, url }) { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [list, setList] = useState([]); const sendQuery = useCallback(async () => { try { setLoading(true); setError(false); const res = await axios.get(url); setList(res.data); setLoading(false); } catch (err) { setError(err); } }, [url]); useEffect(() => { sendQuery(); }, [sendQuery]); return { loading, error, data: list }; } export default useFetch;--useFetchWithoutCallback.js
import { useState, useEffect, useCallback } from "react"; import axios from "axios"; function useFetch({ query, page, url }) { const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [list, setList] = useState([]); const sendQuery = async() => { try { setLoading(true); setError(false); const res = await axios.get(url); setList(res.data); setLoading(false); } catch (err) { setError(err); } } useEffect(() => { sendQuery(); }, [sendQuery]); return { loading, error, data: list }; } export default useFetch; Nota: Hay otros casos de uso para useCallback . Específicamente, no volver a renderizar el componente secundario cuando se usa con React.memo(...)