在自定义钩子中使用useEffect获取的渲染数据问题

时间:2020-05-07 08:35:04

标签: javascript arrays reactjs react-hooks

我是React的新手,并且使用React钩子。我有一个功能组件,需要在页面上显示一些信息。我收到并正确返回jobs数组,我可以使用map对其进行迭代,并成功显示其信息,但也想接收另一个数组:location。该数组基于jobs数组:我需要首先接收jobs,然后获取其位置ID,然后接收信息,并将其放入我的位置数组中,以便以后在我的信息页面中使用。问题是,当我添加位置数组时,我收到多个错误,例如res.json is not a function等。这是我的代码:

function useJobs () {
  const [jobs, setJobs] = React.useState([])
  const [locations, setLocations] = React.useState([])
  React.useEffect(() => {
    fetch('/api/jobs/list-jobs', { headers: headers })
      .then(r => r.json())
      .then(setJobs)
  }, [])
  React.useEffect(() => {
    jobs.map(job => (
      axios.get(`/api/jobs/view-location/${job.location}/`, { headers: headers })
        .then(res => res.json())
        .then(setLocations)
    ))
  }, [jobs], [])

  return (jobs, locations)
}

export default function Jobs () {
  const classes = useStyles()
  const jobs = useJobs()
  const locations = useJobs()
  return (
    <>
   {jobs.map(job => (

          <>
            <div className={classes.root} key={job.id}>
......
<Row>
      <Col style={{ color: 'black' }}>Title:{job.title} </Col>
      <Col>Company Name:{job.company_name} </Col>
      <Col style={{ color: 'black' }}>Internal Code:{job.internal_code} </Col>
 </Row>

{locations.map(location => (
 <Col key={location.id} style={{ color: 'black' }}>Location:{location.country}</Col>))
}

如您所见,在添加location部分之前,我的job信息正确显示。但是当我想根据job.location GET请求提供的信息显示位置信息时,会收到这些错误。我做错了什么?正确的方法是什么?

3 个答案:

答案 0 :(得分:2)

  • 不需要通过res.json()运行axios请求响应。提取请求是必需的。此外,axios响应具有多个信息,并且数据由resp.data

  • 提供
  • useEffect依赖项也只是一个参数,而您传递了两个参数

      React.useEffect(() => {
        axios('/api/jobs/list-jobs', { headers: headers })
          .then(res => setJobs(res.data))
      }, [])
      React.useEffect(() => {
        jobs.map(job => (
          axios.get(`/api/jobs/view-location/${job.location}/`, { headers: headers })
            .then(res => setLocations(prev => ({...prev, [job.id]: res.data})))

        ))
      }, [jobs])
  • 这里要注意的另一件事是useJobs钩子同时返回位置和作业,因此您不需要执行两次,也不需要从useJobs作为对象返回结果,即拥有
    return { jobs, locations }

代替

    return ( jobs, locations )
  • 在更新位置时,您将覆盖位置,请使用对象作为位置

完整代码:

function useJobs () {

  const [jobs, setJobs] = React.useState([])
  const [locations, setLocations] = React.useState({})
  React.useEffect(() => {
    axios('/api/jobs/list-jobs', { headers: headers })
      .then(res => setJobs(res.data))
  }, [])
  React.useEffect(() => {
    for (const job of jobs) {
      axios(`/api/jobs/view-location/${job.location}/`, { headers: headers })
        .then((data) => {
           setLocations(prev => ({...prev, [job.id]: res.data}))
        })
    }
  }, [jobs])

  return [jobs, locations]
}


export default function Jobs () {
  const classes = useStyles()
  const { jobs,locations} = useJobs();
  return (
    <>
      {jobs.map(job => (

          <>
            <div className={classes.root} key={job.id}>
......

        <Row>
              <Col style={{ color: 'black' }}>Title:{job.title} </Col>
              <Col>Company Name:{job.company_name} </Col>
              <Col style={{ color: 'black' }}>Internal Code:{job.internal_code} </Col>

         </Row>


        {locations[job.id].map(location => (
            <Col key={location.id} style={{ color: 'black' 
         }}>Location:{location.country}</Col>))
         }

答案 1 :(得分:1)

此行不返回jobslocations

return (jobs, locations)

相反,它计算jobs,丢弃该结果,计算locations,然后返回该值。

如果要返回包含jobslocations的数组,则可以返回包含它们的数组作为条目:

return [jobs, locations];

并像这样使用它,而不是两次调用useJobs

const [jobs, locations] = useJobs();

或返回包含它们的对象作为属性:

return {jobs, locations};

并再次使用它,而不是两次调用useJobs

const {jobs, locations} = useJobs();

还有其他一些事情可以跳出来:

  1. 正如我在评论中提到的那样,您的代码正在fetch API中成为猎枪的猎物:fetch仅在 network 错误而不是HTTP上拒绝错误;您必须单独检查(通常通过检查response.ok)。详细信息on my anemic little blog

  2. useEffect仅接受最多两个参数,而不是三个。您在这里使用了三个:

    React.useEffect(() => {
      jobs.map(job => (
        axios.get(`/api/jobs/view-location/${job.location}/`, { headers: headers })
          .then(res => res.json())
          .then(setLocations)
      ))
    }, [jobs], [])
    // −−−−−−^^^^−−−−−−−−−−−−−−−− remove
    
  3. 在一个地方使用fetch但在另一个地方使用axios似乎很奇怪。我建议对其中之一进行标准化。除非您有充分的理由使用axios,否则我可能只会在fetch上使用包装器来检查HTTP错误和拒绝。

  4. 我不使用axios,但是Shubham Khatri points out在您的代码中的使用问题。

  5. 您正在将map用作数组的简单迭代器。绝对不合适,map是根据map回调的结果创建一个 new 数组。如果您不使用map的返回值,请改用forEach(或for-of)。

  6. 您正在循环执行每个作业的ajax调用,但随后将结果存储在locations中。这意味着循环中每个调用的结果都会覆盖之前的所有结果。大概您打算对结果的 all 做些事情。

一起考虑:

// A reusable JSON fetch wrapper that handles HTTP errors
function fetchJSON(...args) {
    return fetch('/api/jobs/list-jobs', { headers: headers })
      .then(r => {
          if (!r.ok) {
              throw new Error("HTTP error " + r.status)
          }
          return r.json()
      });
}

function useJobs () {
  const [jobs, setJobs] = React.useState([])
  const [locations, setLocations] = React.useState([])
  React.useEffect(() => {
    fetchJSON('/api/jobs/list-jobs', { headers: headers })
      .then(setJobs)
  }, [])
  React.useEffect(() => {
    // *** Don't use `map` as a simple iteration construct
    for (const job of jobs) {
      fetchJSON(`/api/jobs/view-location/${job.location}/`, { headers: headers })
        .then(setLocations) // *** Need to handle #6 here
    }
  }, [jobs]) // *** No additional array here

  return [jobs, locations]
}

export default function Jobs () {
  const classes = useStyles()
  const [jobs, locations] = useJobs() // *** Use jobs and locations here
  return (
    <>
   {jobs.map(job => (
          <>
            <div className={classes.root} key={job.id}>
            ......
            <Row>
                  <Col style={{ color: 'black' }}>Title:{job.title} </Col>
                  <Col>Company Name:{job.company_name} </Col>
                  <Col style={{ color: 'black' }}>Internal Code:{job.internal_code} </Col>
             </Row>

            {locations.map(location => (
             <Col key={location.id} style={{ color: 'black' }}>Location:{location.country}</Col>))
            }

...但是不能处理#6。

答案 2 :(得分:0)

除了其他答案,您在调用setLocations时也遇到了问题。
因为您是从map函数内部调用的,所以每个作业都调用一次。

jobs.map(job => (
  axios.get(`/api/jobs/view-location/${job.location}/`, { headers: headers })
    .then(setLocations)
))

您实际上是在说:

setLocations('London');
setLocations('Paris');

很明显,如果您两次打来电话,您将失去伦敦,只保留最终位置。

您原本打算将所有位置存储在一个数组中,所以请等待所有诺言完成然后再将它们一起存储。

Promise.all(
  jobs.map(job => (
    axios.get(`/api/jobs/view-location/${job.location}/`, { headers: headers })
        .then(response => response.data)
  ))
)
.then(allLocations => setLocations(allLocations))