React Async Data fetch
3 min readJun 14, 2021
A few React characteristics to keep in mind:
State Update is Asynchronous (slower than what you expect!)
- State updates are asynchronous , so be careful with using the state immediately inside the same function. E.g. in the
handleChange()below, the stateinputis not updated immediately aftersetInputis called. Therefore, if we fetch data using theinputstate, the value ofinputwould actually be before the update!
const handleChange = (event) => { setInput(event.target.value);
fetchData(input); // x , prev input value
fetchData(event.target.value); // o , current value};
- However, the useEffect hook is run after the state updates and DOM updates, our
fetchData()would get the updatedinputvalue if placed inside auseEffecthook
useEffect(()=>{
console.log(input) // o , current input value },[input])const handleChange = (event) => { setInput(event.target.value);
fetchData(input); // x , prev input value
fetchData(event.target.value); // o , current input value};
- If we are calling async/ await
fetchData()insideuseEffect, this would not work inuseEffect(async ()=>{},[]). It is becauseuseEffectexpects a clean-up function or nothing to be returned butasyncfunctions return an implicit promise! We should define theasyncfetchData functions separately instead of using theuseEffectwrapping function - Since
useEffectrequires us to explicitly declare the dependencies in the array, it is best to define thefetchDatafunctions and call it inside the useEffect functions so that we don’t need to worry about declaringfetchDataas a dependency inuseEffect!
useEffect(()=>{
async function fetchData(input) {...}
const data = await fetchData()
setData(data)},[input])const handleChange = (event) => {
setInput(event.target.value)}
Debounce and Re-render
Re-render = Reborn of var, func inside components
- On each component re-render, the functions and the variables inside are created brand new and only the states are updated and preserved across the renders
- Therefore, if we apply the debounced functions simply by itself inside a React component, debounced does not work as expected! Why?
- Debounce works by terminating the intermediate function calls within the timeout period and therefore reduces the total number of functional invocations. e.g. if an user types ‘cat’ within a timeout of 300ms, a debounced onChange handler only invokes the function once with the input ‘cat’ because the previous invocations with input of ‘c’ and ‘ca’ are cancelled
- In React, this presents a dilemma because… 1. on state update, the React component re-renders 2. in each re-render, functions are created brand new and executed 3. It means that, the intermediate invocations of the debounced functions still get executed as the functions are brand new in each render, as they have no memory of the time lapsed from the previous render!
- To solve this issue, we want the same function to be preserved and called throughout the re-rendering phase; therefore, useCallback comes to rescue!
useCallbackreturns a memoized version of the function that only changes when one of the dependencies changes. If the dependency does not change, the same function would be used across re-rendering and debounce as expected!- This gist contains more detailed example
function MySearch() { const [input, setInput] = useState('') // create memoized version of debounced data fetch function const debouncedFetchData = useCallback(debounce(fetchData, 500), []); // call memoized debounced function in the event handler so the
intermediate searches are cancelled by debounce while the user types const handleChange = (event) => {
setInput(event.target.value);
debouncedFetchData(event.target.value);
}; return <input onChange={handleChange} value={input} />}