Common mistakes while fetching in useEffect
Fetching data in React components is a common operation, and the useEffect hook plays a crucial role in managing asynchronous tasks.
Let's see some common mistakes while fetching data in useEffect and also learn the best practices to avoid them.
Not cleaning up the effect
Not cleaning up the effect can lead to memory leaks. For example, let's say you want to subscribe to a data source
// This effect runs after every render
useEffect(() => {
const subscription = dataSource.subscribe()
})
Make sure to unsubscribe from the data source when the component unmounts
useEffect(() => {
const subscription = dataSource.subscribe()
return () => {
subscription.unsubscribe()
}
})
Forgetting the dependency array
useEffect runs on every render by default. This can lead to performance issues. For example, let's say you want to subscribe to a data source but the data is static and won't change based on props or state.
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const data = await fetch('https://api.example.com/data')
setData(data)
}
})
This is a easily avoidable mistake. To fix it, we need to pass an empty array as the second argument to useEffect. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const data = await fetch('https://api.example.com/data')
setData(data)
}
}, [])
Not including all dependencies in the dependency array cause stale data
Adding unnecessary dependencies to the dependency array can also lead to bugs. For example, let's say you want to fetch data from an API based on a prop.
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const data = await fetch(`https://api.example.com/data/${props.id}`)
setData(data)
}
}, [])
This is another common mistake. To fix it, we need to add the prop to the dependency array.
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const data = await fetch(`https://api.example.com/data/${props.id}`)
setData(data)
}
}, [props.id])
Conditional fetching without dependencies
One other variant of same dependency array mistake is conditional fetching without dependencies. For example,
// Incorrect
let shouldFetch = true
useEffect(() => {
if (shouldFetch) {
fetchData()
}
}, [])
// Correct
let shouldFetch = true
useEffect(() => {
if (shouldFetch) {
fetchData()
}
}, [shouldFetch])
Not using useCallback or causing infinite loops
useCallback is a hook that returns a memoized callback. It's useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.
For example, let's say you have a component that fetches data from an API and renders it.
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const data = await fetch('https://api.example.com/data')
setData(data)
}
}, [])
return <div>{data}</div>
Now, let's say you want to pass the fetchData function to a child component. You can't just pass the fetchData function directly because it will be recreated on every render. This will cause the child component to re-render unnecessarily.
const [data, setData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const data = await fetch('https://api.example.com/data')
setData(data)
}
}, [])
return <ChildComponent fetchData={fetchData} />
To fix this, you need to wrap the fetchData function in useCallback.
const [data, setData] = useState(null)
const fetchData = useCallback(async () => {
const data = await fetch('https://api.example.com/data')
setData(data)
}, [])
useEffect(() => {
fetchData()
}, [fetchData])
return <ChildComponent fetchData={fetchData} />
Hope it helps to learn few different variant of mistakes while fetching data in useEffect.
Keep learning and share your comments by tagging me on twitter @learnwithparam 🙌