Home > Enterprise >  How to fetch up-to-date data inside async?
How to fetch up-to-date data inside async?

Time:12-09

I have a function which fetches data.

const fetchData = async (filter) => {
  if (loading) return

  loading = true
  const data = await api(filter)
  setData(data)
  loading = false
}

I also have a filter component, when I change filters it calls my fetchData() function with the new filter variable.

This all works, however there is an issue

This issue occurs when I switch my filter but my fetching function is in a loading state. This causes the if check to fail and I now see outdated data because a re-fetch never happens.

My initial idea was to create a const q = [] variable, and inside if (loading) i would push my filters, and somehow at the end I would re-fetch with the last element inside my q array and then clear that array.

I dont really know how to do that re-fetch logic. A setInterval(() => checkQ(), 1000)? Doesn't seem right

What would be a better approach to take?

CodePudding user response:

You should use an AbortController - that's part of the fetch, as my experience tells me that it's not hard to initiate a new fetch request, but what to do with the first request, when you send out a second?

Here's a snippet that will do the thing you asked - but also deals with the unnecessary requests:

const { useState, useEffect } = React

const useFetchData = () => {
  const [users, setUsers] = useState([])
  
  let controller = null
  
  const fetchData = () => {
    console.log('fetch initiated')
    if (controller) controller.abort()
    controller = new AbortController();
    const { signal } = controller;
    fetch('https://jsonplaceholder.typicode.com/users', { signal })
      .then(response => {
        console.log('request response')
        return response.json()
      })
      .then(json => {
        console.log('retrieved list:', json)
        setUsers(() => json || [])
      })
      .catch(err => {
        if(err.name === "AbortError") {
          console.warn('Abort error', err)
        }
      })
  }
  
  return { fetchData }
}

const FetchData = () => {
  const { fetchData } = useFetchData()
  
  return (
    <div>
      <button onClick={fetchData}>FETCH DATA</button><br />
    </div>
  )
}

const FetchAbortFetchData = () => {
  const { fetchData } = useFetchData()
  
  return (
    <div>
      <button onClick={() => {
        fetchData()
        fetchData()
      }}>FETCH-ABORT-FETCH DATA</button><br />
    </div>
  )
}

const App = () => {
  return (
    <div>
      <FetchData /><br />
      <FetchAbortFetchData />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>

CodePudding user response:

Easiest way is to use your filter criteria as the lock.

Advantages

  1. Always fetching data immediately
  2. Only calling setData with the results from the most recent filter criteria
  3. Simple

Disadvantages

  1. May have multiple simultaneous requests
  2. Toggling back/forth between filters may lead to a race condition.
let latestFilter = null;

const fetchData = async (filter) => {
  // Check to see if its there's the same request already in progress
  // Note: may need to deep equal to check the filter
  if (filter === latestFilter) return;

  // Set the current request as most up to date
  latestFilter = filter

  // Fetch Data (async)
  const data = await api(filter)

  // If the filter is still the most up to date, use it. Otherwise discard.
  // Note: may need to deep equal to check the filter
  if (filter === latestFilter) {
    setData(data)
    latestFilter = null
  }
}

In order to solve disadvantage 2, you can include a counter. This will ensure that only the most recent request will run setData.

let latestFilter = null;
let latestRequestNumber = 0

const fetchData = async (filter) => {
  if (filter === latestFilter) return;
  latestFilter = filter

  // Identify the current request
  const requestNumber = latestRequestNumber   1;
  // Store this number
  latestRequestNumber = requestNumber

  const data = await api(filter)

  // Update data only if we are the most recent request. 
  if (callCount = latestCallCount) {
    setData(data)
    latestFilter = null
  }
}
  • Related