Search code examples
reactjspromiserenderuse-effectrace-condition

api data fetching best practice in react


scenario:

I have two api calls, and they both contribute to the same state, so what I did originally was just await two api calls in the same useEffect. However, one of them is a fairly slow one, so I need to wait extra long time for the page rendering.

const [loading, setLoading] = useState(true)

  useEffect(async() => {
            try{
               slow_api_call_promise = await slow_api_call
               quick_api_call_promise = await quick_api_call
               setLoading(false)
               let newState = quick_api_call_promise.data.merge(slow_api_call_promise.data)
               setState(newState)
               
            }catch(e){
              setLoading(false) 
              //show error message
            }
            
        },[])
    
    return <React.Fragment>
       {loading ? <SpinnerComponent />: <Actual Page />}

    </React.Fragment>

fortunately, the quick one actually provides most of the state I need for initial rendering, and the slow one contributes to just part of the page state. So for rendering experience, I separated them into two useEffect and set different loading state for them. It seems to work. but this looks silly to me, it is going to render twice. is there a better way, an optimized way to approach this.

const [loadingWhole, setLoadingWhole] = useState(true)
const [loadingPart, setLoadingPart] = useState(true)

useEffect(async() => {
        quick_api_call_promise = await quick_api_call
        setLoadingWhole(false)
    },[])

    useEffect(async() => {
        slow_api_call_promise = await slow_api_call
        setLoadingPart(false)
    },[])

Solution

  • Yes, you can keep a single effect, just do a first setState already after you've fetched the quick response:

    const [state, setState] = useState(null);
    const [loadingWhole, setLoadingWhole] = useState(true);
    const [loadingPart, setLoadingPart] = useState(true);
    
    async function fetchResults() {
        const quickResult = await callQuickApi();
        setState(quickResult.data);
        setLoadingPart(false);
    
        const slowResult = await callSlowApi();
        let newState = merge(quickResult.data, slowResult.data);
        setState(newState);
        setLoadingWhole(false);
    }
    
    useEffect(async() => {
        fetchResults().catch(err => {
            setLoadingPart(false);
            setLoadingWhole(false);
            //show error message
        });
    },[]);
    

    Btw, instead of 4 separate useState hooks you might want to consider using just one, which can be in 5 states only not a multitude of combinations (even !loadingWhole && loadingPart that doesn't make sense):

    • loading
    • error
    • partial response + loading
    • partial response + error
    • full response