Куда делись мои данные? {React Router}

В этом посте я расскажу о запутанном гнезде проблем, с которыми я столкнулся, изучая React Router, и которые потребовали некоторого времени на отладку и устранение неполадок.

Для начала нам с коллегой-разработчиком было поручено создать одностраничное приложение, использующее маршрутизацию на стороне клиента для отображения разных «страниц» в DOM при каждом маршруте.

Мы создали приложение, которое обращается к бесплатному API Pokémon для отображения библиотеки покемонов в качестве одного маршрута и личного покедекса в качестве другого маршрута. В библиотеке перечислены первые 386 покемонов из API, а в покедексе — все покемоны, которых пользователь сохранил (или поймал, если хотите). Пользователь также может нажать на любого из покемонов, чтобы перейти на страницу с подробным описанием этого покемона. Каждый маршрут имеет свой собственный уникальный URL, включая маршрут к определенному покемону (например, при нажатии на пикачу URL изменится на localhost:####/pikachu).

Все было хорошо, пока мы не попробовали проложить маршрут к подробной странице покемонов, вручную введя покемона в URL. Данные о покемонах не передавались, и страница ничего не показывала. Возникал вопрос: куда делись наши данные?????

Проиллюстрируем нашу схему:
Наш родительский компонент App содержит маршрутизацию к нашим компонентам Library, Pokédex и Pokémon Page. В App мы получаем данные из API с помощью хука useEffect и храним их в состоянии. Затем мы передаем эти данные всем трем дочерним компонентам в качестве реквизитов, чтобы они были доступны для отображения данных в этих компонентах.

function App() {
  ... //fetched data
  return (
    <div>
      <NavBar />
      <Switch>
        <Route exact path ="/">
          <HomePage />
        </Route>
        <Route exact path="/library">
          <Library
            pokemons={pokemons} //passing props to Library
          />
        </Route>
        <Route exact path="/pokedex">
          <Pokedex
            pokemons={pokemons} //passing props to Pokedex
          />
        </Route>
        <Route exact path="/:name">
          <PokemonPage 
            pokemons={pokemons} //passing props to PokemonPage
          />
        </Route>
      </Switch>
    </div>
  );
Вход в полноэкранный режим Выход из полноэкранного режима

Забавно, что когда мы вручную вводим URL-адрес для компонентов Library и Pokédex, они загружаются, как и ожидалось. И только когда мы вручную вводим URL для конкретного покемона, страница покемонов ничего не отображает.

Чтобы устранить неполадки, я установил несколько отладчиков в родительском и дочернем компонентах, чтобы попытаться проследить путь, который проходит код. Я также разместил консольный журнал реквизитов в компоненте Pokémon Page, чтобы проверить, проходит ли он (не проходит), а также консольные журналы в определенных местах, чтобы увидеть, какие строки кода попадают в них. И тогда я заметил кое-что интересное.

Важная справочная информация:
Когда мы изначально получаем данные в компоненте App, мы сделали так, что два получения происходят асинхронно. Это было необходимо из-за того, как устроен API. Первая выборка предоставляет только имена покемонов, поэтому мы используем вторую выборку для итерации имен, чтобы получить подробную информацию о каждом покемоне. Поскольку первая выборка должна завершиться до того, как начнется вторая, мы использовали async и await для достижения этой цели. Это вызвало непрерывную установку состояния, заставив наш код ходить туда-сюда между рендерингом компонентов App и Pokémon Page, чтобы Pokémon Page загружала только фрагменты данных за один раз.

function App() {
  const getPokemons = async () => {
    const res = await fetch({pokeAPI url})
    const data = await res.json()

    const createPokemonObject = (results) => {
      results.map(async pokemon => {
        const res = await fetch({pokeAPI url by pokemon name})
        const data = await res.json()
        setPokemons(pokemons => [...pokemons, data])
      })
    }

    createPokemonObject(data.results)
  }

  useEffect(() => {
    getPokemons()
  }, [])

  ... //returned JSX
}
Вход в полноэкранный режим Выход из полноэкранного режима

Причина, по которой это мешало компоненту Pokémon Page, заключается в том, что задержка в получении данных привела к тому, что компонент сначала загрузился с пустыми реквизитами. Это не позволило нашим элементам изображения отрисоваться из-за неопределенных источников (которые пришли бы из реквизитов) и, в свою очередь, остановило отрисовку нашего компонента. Даже если мы знаем, что данные технически будут отправлены позже, компонент этого не знает. Поэтому он не пытается повторно отрисовать компонент после того, как считает, что в первый раз это не удалось.

Решение, которое я придумал для решения этой проблемы, заключалось в применении оператора if, который оборачивает наш JSX, чтобы вернуть другой JSX (например, текст ‘Loading…’), если реквизит не определен, в противном случае возвращается наш оригинальный подробный JSX страницы. Это позволило странице продолжать попытки рендеринга асинхронно получаемых данных, обманывая компонент и заставляя его думать, что он ничего не сделал, и продолжать попытки.

if (pokemon === undefined) {
  return <h1>Loading...</h1>
} else {
  return (
    ... //JSX for the detailed page
  )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Оцените статью
devanswers.ru
Добавить комментарий