šŸŽ Checkout my Learn React by Making a Game course and get 1 month Free on Skillshare!

Optimizing React useEffect: preventing multiple API calls (fetch race conditions)

Race conditions while doing multiple sequential API calls in React useEffect() can cause some strange behaviors and performance issues.

Let's take the below code:

const App = () => {
    const [id, setId] = useState(1)

    const handleClick = ()=> setId(prev => prev + 1)

    return <div>
        <button onClick={handleClick}>Next</button>
        <Article id={id} />
    </div>
}

const Article = ({id}) => {
    const [text, setText] = useState('')

    useEffect(()=> {
        fetch('https://jsonplaceholder.typicode.com/posts/' + id)
            .then( resp => resp.json())
            .then( resp => setText(resp.body))
    }, [id])

    return (<div>{text}</div>)
}

We just fetch a new article at the press of a button.

While this works great at first glance, things will break if the user presses the Get Next Article button very fast multiple times.

To make it easier to spot you can set up slow 3G mode in the developer's tools.

React useEffect: preventing multiple API calls fetch race conditions

This is quite a common problem as we get multiple chained API calls in useEffect() and therefore we end up with a race condition.

One alternative to fix this issue is by using a boolean value, as described in this video.

While this works, I think a nicer approach will be to use a Javascript AbortController, to cancel the ongoing API calls.

const {useState, useEffect} = React

const App = () => {
    const [id, setId] = useState(1)
    const handleClick = ()=> setId(prev => prev + 1)
    return <div>
        <Article id={id} />
        <button onClick={handleClick}>Get Next Article</button>
    </div>
}

const Article = ({id}) => {
    const [text, setText] = useState('')

    useEffect(()=> {
        const abortCtrl = new AbortController()
        const fetchOtps = {signal: abortCtrl.signal}
        fetch('https://jsonplaceholder.typicode.com/posts/' + id, 
            {signal: abortCtrl.signal})
                .then( resp => resp.json())
                .then( resp => setText(resp.body))

        // the cleanup function runs 
        // each time useEffect() is exectued
        return () => abortCtrl.abort()
    }, [id])

    return (<div>{text}</div>)
}

The cleanup function of useEffect() is called, not only when the component is unmounted but also at each new run of useEffect().

With this approach we avoid the text flickering, but we also drop any ongoing fetch requests.

I hope you will find this useful. As usual, you can check out the full code on my Github and the running example here.

šŸ“– 50 Javascript, React and NextJs Projects

Learn by doing with this FREE ebook! Not sure what to build? Dive in with 50 projects with project briefs and wireframes! Choose from 8 project categories and get started right away.

šŸ“– 50 Javascript, React and NextJs Projects

Learn by doing with this FREE ebook! Not sure what to build? Dive in with 50 projects with project briefs and wireframes! Choose from 8 project categories and get started right away.


Leave a Reply

Your email address will not be published. Required fields are marked *