0

I need to fetch json data and iterate over it, fetching some more json data later, and finally setting React states. I'd like to use a general function to handle all file requests. I'd also like to handle different types of errors (404 and json parsing, mostly).

I've read How to use async functions in useEffect and several other relevant articles, but whatever I try to do outside of my fetchData function, I keep getting only Promises instead of data.

Also, this only needs to be done once, when the main App.js load. Is it good practice to use an Effect for that, or should it be done at the top of the file, before the App component itself (which needs the data)?

  useEffect(() => {
    async function fetchData(url) {
      const response = await fetch(url, {
        headers : { 
          'Content-Type': 'application/json',
          'Accept': 'application/json'
          }
      })
      if (!response.ok) {
        return Promise.reject(`File not found: ${url}`)
      } else {
        const json = await response.json()
          .then(res => { return res })
          .catch(err => { return Promise.reject(`JSON Error (${url}): ${err}`) })
        // I can use "json" here...
      }
    }

    // ...but I need it here
    const json = fetchData('data/initial-data.json')
      .catch(err => console.log('Error ' + err))
    // to iterate over its contents with more calls to fetchData
    for (const item in json) {
      const moreData = fetchData(`data/moredata-${item}.json`)
        .catch(err => console.log(`Error: ${err}`))
      // do something with moreData
      // such as setState
    }
  }, [])
Cirrocumulus
  • 520
  • 3
  • 15
  • 1
    TL;DR `fetch()` does not return a rejected promise when the response status is unsuccessful. You need to check `a.ok` first – Phil May 22 '23 at 06:25
  • Thank you. I'm deleting the question. – Cirrocumulus May 22 '23 at 06:35
  • There's no need to delete it. You won't be penalised and it can act as a signpost for other users with similar problems. See https://stackoverflow.com/help/duplicates – Phil May 22 '23 at 06:35
  • Is it OK if I rephrase the question with more details because it's a bit more involved than that, and although your response helped I'm banging my head against Promises for the last 24 hours in a rather simple scenario. – Cirrocumulus May 22 '23 at 07:53
  • 1
    Looks like youare mixing async/await with `then()`, I think you should use one or ther other, not both at the same time. – Camilo May 24 '23 at 18:25

1 Answers1

1

You just need to return the json from the fetchData function and await it where you need it:

  useEffect(() => {
    async function fetchData(url) {
      const response = await fetch(url, {
        headers : { 
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        }
      })

      if (!response.ok) {
        throw new Error(`Failed to fetch ${url}, got ${response.status}`)
      }

      // note the return
      return await response.json()
    }

    // Note need the IIFE wrapper here because you
    // can't return a Promise from the useEffect callback
    (async function() {
      try {
        const initialData = await fetchData(whatever)
        const allData = await Promise.all(initialData.map(async (data) => {
           try {
             // fetch and return more data
             const moreData = await fetchData(data.whatever)
             return moreData
           } catch (err) {
             // do something with error, can return it or some
             // sentinel value like null
             return err
           }
        }))

        // setState, filtering out the failures
        setState(allData.filter(x => !(x instanceof Error))
      } catch (err) {
        console.error('failed initial fetch')
      }
    })()
  }, [])

On a side note don't mix .then/.catch with async/await in the same expression, and don't reject with strings as many Promise rejection handling code implicitly assumes the thing that caused the rejection is an actual error instance. If you wouldn't throw it don't reject a Promise with it.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • Thank you. I copied the entire code block to try and understand it, but there seems to be a syntax error somewhere. I believe you are missing a closing parenthesis at the end of the `try/catch` block inside `Promise.all`, which should end with `}))`. But more importantly, I get an "unexpected keyword `await` right after `const moreData`. – Cirrocumulus May 25 '23 at 06:03
  • Also, I believe `got ${response.statusCode}` should return `response.status` or `response.statusText`, as `statusCode` is not defined. – Cirrocumulus May 25 '23 at 06:21
  • Seems to work if you add `async` before the `(data)` function inside `map`. – Cirrocumulus May 25 '23 at 07:22
  • Good catches, I dashed that off right after work and got distracted by life. Fixed. – Jared Smith May 25 '23 at 11:20