Работа с хуками и 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;