0

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)
    },[])
  • Just don't `await` the slow api call... it'll still render twice but your code will be much less repetitive – Michael Abeln Nov 11 '21 at 19:19
  • 1
    "*This looks silly to me, it is going to render twice*" - but that's exactly what you wanted? – Bergi Nov 11 '21 at 19:28
  • Notice you [cannot pass an `async` function to `useEffect` anyway](https://stackoverflow.com/q/53332321/1048572). So yes, just do two calls in one effect. – Bergi Nov 11 '21 at 19:32
  • @MikeAbeln I have to choose this way, because the slower api call takes around 10 seconds, user won't tolerate a first paint time as 10 seconds – Xiaoye Yang Nov 14 '21 at 00:15
  • @Bergi yeah, I need both apis, and the slower one is really slow. so first api will return most of what i want, and for second api I put a loader on the stuff it is trying to load, so yeah logically, there will be two renders. But just wondering if there is any other more cleaner way, like hydrating, or combine these two useEffect into one or etc. – Xiaoye Yang Nov 14 '21 at 00:27
  • Not sure what you mean by "hydrating". But yes combining the effects is trivial. Btw, what are these variables that you assign the results to? – Bergi Nov 14 '21 at 00:32
  • @Bergi just variable names, I am gonna use the data attribute on the variable to construct my state. but the render time is still too much if combined into one – Xiaoye Yang Nov 14 '21 at 00:40
  • @XiaoyeYang No? Can you please [edit] your post to include the actual code, and especially the code of how you attempted to combine them into one? – Bergi Nov 14 '21 at 00:42

1 Answers1

1

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
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • will react batch the updates? – Xiaoye Yang Nov 14 '21 at 01:45
  • Depends on the react version iirc. It might batch the `setState` together with the `setLoading`, or it might not, that's why I suggested to use a single state. It will never batch the first update that happens before the `await` together with the second update that happens after the `await`. – Bergi Nov 14 '21 at 01:51
  • Thank you so much! could you explain why it will never batch the first update with second update? all i know is react will do batch updates in lifecycle method or event handlers(where react handle things). Or could you point me to some documents or examples or source code? – Xiaoye Yang Nov 14 '21 at 02:03
  • 1
    Because after the first `await`, it's no longer react handling things, you've left the lifecycle method. It's the (implicit) promise handler that runs the code and makes the `setState` calls. (It might be more obvious if you try rewriting the code from `async`/`await` to `.then()` syntax). – Bergi Nov 14 '21 at 02:08