Проблемы с отображением данных, полученных с помощью useEffect, в настраиваемом хуке - PullRequest
1 голос
/ 07 мая 2020

Я новичок в React и использую React hooks. У меня есть функциональный компонент, который должен отображать некоторую информацию на странице. Я получаю и возвращаю массив jobs правильно, и я могу выполнить итерацию в нем с помощью map и успешно показать его информацию, но также хочу получить другой массив: location. этот массив основан на массиве jobs: сначала мне нужно получить jobs, затем получить его идентификатор местоположения, а затем получить информацию и поместить ее в свой массив местоположений, который будет использоваться на моей информационной странице позже. проблема в том, что когда я добавляю массив местоположений, я получаю несколько ошибок, например 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 ]

3 голосов
/ 07 мая 2020
  • Ответ на запрос ax ios не нужно пропускать через res.json(). Это необходимо для получения запроса. Также ответ 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 голос
/ 07 мая 2020

Эта строка не возвращает jobs и locations:

return (jobs, locations)

Вместо этого она оценивает jobs, отбрасывает этот результат, оценивает locations и возвращает это значение.

Если вы хотели вернуть массив, содержащий jobs и locations, вы можете либо вернуть массив, содержащий их как записи:

return [jobs, locations];

, и использовать его так, а не делать два вызывает useJobs:

const [jobs, locations] = useJobs();

или возвращает объект, содержащий их в качестве свойств:

return {jobs, locations};

и используйте его так, опять же, вместо того, чтобы делать два вызова useJobs:

const {jobs, locations} = useJobs();

Есть еще пара вещей, которые выпрыгивают:

  1. Как я уже упоминал в комментарии, ваш код становится жертвой ножного оружия. в fetch API: fetch отклоняет только ошибку сеть , но не ошибку HTTP; вы должны проверить это отдельно (обычно проверяя response.ok). Подробности в моем анеми c маленький блог .

  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, но Шубхам Хатри указывает на проблемы с его использованием в вашем коде.

  5. Вы используете map как простой итератор для массива . Это никогда не подходит, map предназначен для создания нового массива из результатов обратного вызова map. Если вы не используете возвращаемое значение из map, используйте вместо него forEach (или for-of).

  6. Вы выполняете вызов ajax на задание в al oop, но затем сохраняя результаты в locations. Это означает, что результаты каждого вызова в l oop перезаписывают все предыдущие результаты. Предположительно вы хотели что-то сделать с всеми результатами.

Взяв все вместе:

// 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.

0 голосов
/ 07 мая 2020

В дополнение к другим ответам у вас также есть проблема с вызовом setLocations.
Поскольку вы вызываете его изнутри функции карты, вы вызываете ее один раз для каждого задания.

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

Фактически вы говорите:

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

Ясно, что если вы вызовете это дважды, вы потеряете Лондон и сохраните только последнее местоположение.

Вместо этого вы хотели сохранить все местоположения в массив, поэтому дождитесь завершения всех обещаний sh, а затем сохраните их все вместе.

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