useCallback memoizes the function definition, so that each time a component re-renders it won't create a new definition for that function unless the dependency array fed to useCallback changes.
Here, the function with useCallback is included in a dependency array for the useEffect. Without a memoization, each time this hook is called, it will create a new function definition for searchQuery and will render again, resulting in an infinite loop.
To visualize this I have created a codesandbox example, open up console and see that the component that doesn't use the useCallback renders infinitely.
The Code:
-- Query.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;
Note: There are other use cases for useCallback. Specifically, not re-rendering child component when it is used with React.memo(...)