UseEffect не ждет Promise.all?

Я хочу получить данные для каждого объекта в массиве, переданном как реквизит (rootComments), и добавить данные в свойства объекта:

useEffect(() => {
  rootComments.map(rootComment => {
    if (rootComment.kids) {
      rootComment.kidsText = []
      fetchKidsText(rootComment.kids)
    }
  })
  console.info(rootComments)
  rootComments.map(comment => console.info(Object.values(comment)))
}, [rootComments])

const fetchKidsText = async (ids) => {
  await Promise.all(
    ids.map(id => fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`).then(response => response.json()).then(result => {
      rootComments.map(comment => {
        if (comment.id === result.parent) {
          comment.kidsText = comment.kidsText.concat(result.text)
        }
      })
    }))
  );
}

Похоже, это не работает, я не могу отобразить rootComments.map(comment => comment.kidsText) и не могу зарегистрировать его как rootComments.map(comment => console.info(Object.values(comment))) (действует так, как будто нет нового свойства kidsText) или как rootComments.map(comment => console.info(comment.kidsText)) (возвращает пустой массив). Но. Когда я регистрируюсь console.info(rootComments), он возвращает массив объектов с новым свойством kidsText.

Моя первая мысль заключалась в том, что useEffect() не ждет, пока rootComments будет извлечен внутри другого компонента, но это не кажется проблемой, так что, может быть, это что-то внутри fetchKidsText() или то, как он взаимодействует с useEffect()?

В вашем коде много чего не так. Я думаю, что текущий ответ @kigiri довольно хорошо резюмирует эти проблемы.

Link 09.12.2020 18:46
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
3
1
682
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Несколько вещей неверны в этом коде:

  • Вы мутируете свой объект rootComments без использования setState (реакция не может обновляться с него)
  • Вы используете map для итерации, но отбрасываете результат
  • Сочетание .then в функции async / await
  • Вы не возвращаете значение Promise.all, а вместо этого await его

.map не является циклом for, используйте либо .forEach, либо цикл for..of

Что касается вашего фактического вопроса, мой первый вопрос, вероятно, является основной проблемой, покажите больше кода, чтобы мы могли подтвердить

отредактируйте предложенный альтернативный код:

useEffect(() => {
  // get all the kids id, this create a single big array with every kids:
  const allKids = rootComments.flatMap(rootComment => rootComment.kids || [])

  // use a controller too cancel fetch request if component is unmounted
  // otherwhise, trying to setState if the component is unmounted will
  // mess react up:
  const controller = new AbortController()

  // we fetch all the kids at once instead of doing this in a map like before
  fetchKidsText(allKids, controller.signal)
    .then(setRootComment) // note that i'm using `.then` here,
    // because we can't await in hooks we must return a callback or undefined.
    .catch(err => {
      if (err.name === 'AbortError') return // we can safely ignore abort error
      // ? handle error here
    })
  return () => controller.abort()
}, [rootComments])

const fetchKidsText = async (ids, signal) => {
  // first we call the api and get everything a big result
  const results = await Promise.all(ids.map(async id => {
    const url = `https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`
    const res = await fetch(url, { signal })
    return res.json()
  }))

  // now what you were trying to do was merge every children text comment
  // since we got them all, just one filter should work:
  return rootComments.map(comment => {
    const children = results.filter(r => r.parent === comment.id)
    const kidsText = children.map(r => r.text)
    return { ...comments, kidsText }
  })
}

Не используйте этот код как есть, я не проверял его, просто чтобы объяснить, как я попытался бы решить вашу проблему с комментариями, чтобы объяснить свое мнение.

Удачи !

Другие вопросы по теме