The original problem: useEffect missing dependency

This is the original code, which leads to a warning “useEffect has a missing dependency fetchData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default function useGet(url, initialState = null) {

const [data, setData] = useState(initialState);
const [isLoading, setLoading] = useState(false);
const reFetch = async () => {
await fetchData()
}

const fetchData = async () => {
setLoading(true);
const response = await axios.get(url);
setData(response.data);
setLoading(false);
}

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

return { data, isLoading, reFetch};
}

Why this problem: useEffect’s skipping effect optimization

As emphasized in React documentation:

If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect.

fetchData is a component scope value, and lint is warning that if fetchData is changed, the useEffect will skip, potentially causing an out-of-sync.

Add fetchData to useEffect dependency

The immediate solution you would think of is to add fetchData to the useEffect dependencies:

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

The problem here is that fetchData is a local const and will be evaluated with each render. This will case useEffect being triggered more times than needed.

Memorize the callback

  • Given an inline callback and an array of dependencies, useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.
  • Here we know fecthData depends on url and useEffect depends on fetchData. Now we have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default function useGet(url, initialState = null) {

const [data, setData] = useState(initialState);
const [isLoading, setLoading] = useState(false);
const reFetch = async () => {
await fetchData()
}

const fetchData = useCallback(async () => {
setLoading(true);
const response = await axios.get(url);
setData(response.data);
setLoading(false);
}, [url])

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

return { data, isLoading, reFetch};
}
  • The dependency chain is useEffect -> fetchData -> url.
  • fetchData will only be reloaded if url changes
  • useEffect will only be triggered if fetchData changes.

Readings