设置React状态从JSON数据在useEffect中

huangapple go评论77阅读模式
英文:

Setting React state from JSON data in useEffect

问题

我需要获取 json 数据并对其进行迭代,稍后获取更多的 json 数据,最后设置 React 状态。我想使用一个通用函数来处理所有文件请求。我还想处理不同类型的错误(主要是 404json 解析错误)。

我已经阅读了 如何在 useEffect 中使用异步函数 和其他相关文章,但无论我尝试在 fetchData 函数之外做什么,我始终只得到 Promises 而不是数据。

此外,这只需要在主 App.js 加载时执行一次。是否使用 Effect 来实现这个好,还是应该在文件的顶部,在 App 组件本身之前执行(需要这些数据)?

  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}`) })
        // 我可以在这里使用 "json"...
      }
    }

    // ...但我需要在这里使用它
    const json = fetchData('data/initial-data.json')
      .catch(err => console.log('Error ' + err))
    // 通过更多的 fetchData 调用迭代其内容
    for (const item in json) {
      const moreData = fetchData(`data/moredata-${item}.json`)
        .catch(err => console.log(`Error: ${err}`))
      // 对 moreData 做一些操作
      // 比如设置状态
    }
  }, [])
英文:

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
    }
  }, [])

答案1

得分: 1

以下是您要翻译的代码部分:

  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}`)
      }

      // 注意这里的返回
      return await response.json()
    }

    // 注意需要在这里使用IIFE包装,因为您不能从useEffect回调中返回Promise
    (async function () {
      try {
        const initialData = await fetchData(whatever)
        const allData = await Promise.all(initialData.map(async (data) => {
          try {
            // 获取并返回更多数据
            const moreData = await fetchData(data.whatever)
            return moreData
          } catch (err) {
            // 处理错误,可以返回错误或一些哨兵值,比如null
            return err
          }
        }))

        // 设置状态,过滤掉失败的部分
        setState(allData.filter(x => !(x instanceof Error)))
      } catch (err) {
        console.error('failed initial fetch')
      }
    })()
  }, [])

另外,注意不要在同一个表达式中混合使用.then/.catchasync/await,并且不要使用字符串拒绝Promise,因为许多Promise拒绝处理代码隐含地假设导致拒绝的事物是一个实际的错误实例。如果您不会使用throw,就不要用它来拒绝Promise。

英文:

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.

huangapple
  • 本文由 发表于 2023年5月22日 14:20:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/76303468.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定