Работа с хуками и api

Пытаюсь разобраться, как работает react + hooks.

ВОПРОС: почему массив данных прилетает только при загрузке второй страницы? и как сделать, что бы при перезагрузки страницы отобразились данные?

  • Проблема у меня состоит в том, что когда я загружаю страницу, не всегда из первого раза возвращается массив данных. есть api покемонов (pokeapi.co) в котором хранятся данные по разным ссылкам. Эти данные я хочу совместить. (по одной ссылке мы берем url покемонов next/prev page страницы, а из другой достаем информацию по каждому покемону по ссылке, которую мы взяли из первой ссылкам и выводим в браузер).
  • Вторая проблема заключается в том, что при первом рендеринге нужно
    как-то загрузить информацию про покемонов, так как загрузка данных при первом рендеринге из useEffect не работает (думаю над этим, т.к при
    другой реализации у меня происходит зацикливание и ложится браузер)

Искал решения в примерах на просторах Google, так кроме стандартного примера с документации React толком ничего не нашел.

import React, { useState, useEffect } from 'react'
import axios from 'axios'



// Функция, которая рендерит и возвращает разметку блоков покемона
const PokemonRender = (data) => {
  const list = data.map(param => (
    <div key={param.name}>
      <h2>#{param.id}</h2>
      <img src={param.images} alt="pokemon_img" />
      <h3>{param.name}</h3>
      {/* <h4>{param.type.join(`, `)}</h4> */}
      {/* <table>
        <tbody>
          <tr><td>HP:</td><td>{param.stats[0]}</td></tr>
          <tr><td>Attack:</td><td>{param.stats[1]}</td></tr>
          <tr><td>Defense:</td><td>{param.stats[2]}</td></tr>
          <tr><td>Special-attack:</td><td>{param.stats[3]}</td></tr>
          <tr><td>Special defense:</td><td>{param.stats[4]}</td></tr>
          <tr><td>Speed:</td><td>{param.stats[5]}</td></tr>
        </tbody>
      </table> */}
    </div>
  ))
  return list
}

// Сердце блоков покемонов
function Page() {
  const [loading, setLoading] = useState(true)
  const [page, setPage] = useState(`https://pokeapi.co/api/v2/pokemon/?offset=0&limit=100`)
  const [nextpage, setnextPage] = useState()
  const [previouspage, setpreviousPage] = useState()
  const [URL, setURL] = useState([])
  const [pokemon, setPokemon] = useState(['инициализация', 'будет когда-то'])


  useEffect(() => {
    setLoading(true)
    axios.get(page).then(res => {
      setLoading(false)
      setnextPage(res.data.next)
      setpreviousPage(res.data.previous)
      setURL(res.data.results.map(r => r.url))
      **setPokemon(URL.map(link => pokemonInfo(link))) // почему-то сюда данные прилетают только на второй странице**
      console.log(pokemon)
    })
  }, [page])
  if (loading) return (<Loading />)

  return (
    <div>
      <div>
        {URL.map(p => <p key={p}>{p}</p>)}
      </div>
      <div>
        page: {page}
        <h5>next: {nextpage}</h5>
        <h5>prev: {previouspage}</h5>
      </div>
      <button onClick={() => setPage(previouspage)}>prev</button>
      <button onClick={() => setPage(nextpage)}>next</button>
    </div>
  )
}

// сюда нужно будет записать форму загрузки
const Loading = () => {
  return <h1>Мы не загрузились</h1>
}

// Функция, которая возвращает объект характеристик покемона
const pokemonInfo = (url) => {
  let data = {}
  axios.get(url).then(res => {
    data.id = res.data.id
    data.name = res.data.name
    data.images = res.data.sprites.other.dream_world.front_default
    data.type = res.data.types.map(p => p.type.name)
    data.stats = res.data.stats.map(p => p.base_stat)
  })
  return data;
}

function App() {
  return (
    <div>
      <Page />
    </div>

  );
}

export default App;

Может есть и другой способ реализации, но пока я вижу только такой.


Ответы (1 шт):

Автор решения: Konstantin Modin

Проблема в том что setURL, setPokemon работают асинхронно. То есть после их вызова в useEffect еще не обновлены переменные URL и pokemon. Прилагаю работающий вариант кода. С разметкой конечно можно еще поработать, но логика работает.

import React, { useState, useEffect } from "react";
import axios from "axios";

const Page = () => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [page, setPage] = useState(
    `https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20`
  );
  const [nextpage, setNextPage] = useState("");
  const [previouspage, setPreviousPage] = useState("");
  const [pokemon, setPokemon] = useState(null);
  const [currentPokemon, setCurrentPokemon] = useState(null);

  useEffect(() => {
    setLoading(true);
    axios(page)
      .then((res) => {            
        setLoading(false);
        setNextPage(res.data.next);
        setPreviousPage(res.data.previous);
        setPokemon(res.data.results);
      })
      .catch((error) => {
        setLoading(false);
        setError(error);
      });
  }, [page]);

  if (loading) return <h1>Loading...</h1>;
  if (error) return <div>Error</div>;

  return (
    <div>
      <div>
        <h2>Pokemon List:</h2>
        {pokemon &&
          pokemon.map(({ name, url }) => (
            <div key={url}>
              <button onClick={() => setCurrentPokemon(url)}>{name}</button>
            </div>
          ))}
      </div>
      <button onClick={() => setPage(previouspage)}>Prev page</button>
      <button onClick={() => setPage(nextpage)}>Next page</button>
      <PokemonInfoRender url={currentPokemon} />
    </div>
  );
};

const PokemonInfoRender = ({ url }) => {
  const [data, setData] = useState(null);
  useEffect(() => {
    axios(url).then((res) => {
      setData(res.data);
    });
  }, [url]);

  if (!data) return null;

  const { name, types, stats, sprites } = data;

  return (
    <div>
      <h1>Pokemon info</h1>
      <h2>{name}</h2>
      <img src={sprites.other.dream_world.front_default} alt="pokemon_img" />
      <div>
        <h3>Types:</h3>
        {types.map(({ slot, type }) => (
          <div key={slot}>
            <div>Slot: {slot}</div>
            <div>Type: {type.name}</div>
          </div>
        ))}
      </div>
      <div>
        <h3>Stats:</h3>
        {stats.map(({ base_stat, effort, stat: { name } }) => (
          <div key={name}>
            <div>Name: {name}</div>
            <div>Base stat: {base_stat}</div>
            <div>Effort: {effort}</div>
          </div>
        ))}
      </div>
    </div>
  );
};

const App = () => {
  return (
    <div>
      <Page />
    </div>
  );
};

export default App;
→ Ссылка