Не отображается на экране информация при нажатии на кнопку
При нажатии на кнопку хочу получить информацию об отдельной стране. Запрос отправляется успешно, результат запроса сохраняется в массив (строка 6). Но почему-то в console.log (строка 19) я получаю пустой массив и на экране информация не отображается. Ошибок в консоли нет. Если я пишу название страны в строке поиска целиком, то информация о ней отображается на экране. Что я делаю не так?
import { useState } from 'react'
import Filter from './Components/Filter'
import axios from 'axios'
const ShowCountries = ({countries}) => {
const[state, setState] = useState([])
const capital = state.find(arr => arr[0] === 'capital')
const area = state.find(arr => arr[0] === 'area')
const languages = state.find(arr => arr[0] === 'languages')
const flags = state.find(arr => arr[0] === 'flags')
if(countries.length === 0) return <p>Loading...</p>
const countryInfo = (country) => {
const baseUrlItem = `https://studies.cs.helsinki.fi/restcountries/api/name/${country}`
axios.get(baseUrlItem).then(response => {
setState(Object.entries(response.data))
console.log(state)
}
)
if(state.length>0)
return <div>
<h1>{country}</h1>
<p>{capital[0]} {capital[1]}</p>
<p>{area[0]} {area[1]}</p>
<p>{languages[0]} :</p>
<ul>
{Object.values(languages[1]).map(i => <li key={i}>{i}</li>)}
</ul>
<img src={flags[1]['png']}/>
</div>
}
if(countries.length === 1) return countryInfo(countries)
if(countries.length > 10) return 'Too many matches, specify another filter'
if(countries.length > 1 && countries.length < 10) {
return countries.map((country) => <div key={country}>
<b>{country}</b>
<button onClick={() => countryInfo(country) }>show</button><br></br>
</div>)
}
}
const App = () => {
const [countries, setCountries] = useState([])
const [findCountrieValue, setFindCountrieValue] = useState('')
const baseUrl = 'https://studies.cs.helsinki.fi/restcountries/api/all'
const findCountrie = (event) => {
setFindCountrieValue(event.target.value)
axios.get(baseUrl).then(response =>
setCountries(response.data
.map(c => c.name.common).filter(c => c.toUpperCase()
.includes(findCountrieValue.toUpperCase()))))
}
return (
<div>
<Filter find={findCountrie} value={findCountrieValue}/>
<ShowCountries countries={countries} setCountries={setCountries}/>
</div>
)
}
export default App
Ответы (1 шт):
Начнем с ошибок, допущенных в коде:
метод includes ищет полное совпадение элемента проверяемым данным, в вашем случае, метод вернет true только если название страны введено полностью. Для поиска подстроки, можно использовать метод indexOf.
Компонент ShowCountries принимает список стран и отображает их названия, однако показывать детализацию будет только в том случае, если найдена только одна страна:
if(countries.length === 1) return countryInfo(countries)
Когда стран больше, то будет показан список стран, а результат работы функции countryInfo "улетит в космос", т.к. выполняется в другой функции(обработчик события нажатия кнопки) и не будет являться результатом рендера.
Постарайтесь работать с теми данными, которые к вам приходят. Вы зачем то объект превратили в массив и далее пытаетесь поиском по длинному массиву найти нужную информацию методом find.
В дальнейшем это может сказаться на производительности вашего приложения.
Компоненты в React должны опираться на свое состояние и рендерить данные исходя из него(У вас ShowCountries отрисовывает данные исходя из переданных параметров - props'ов, а изменение собственного состояния state не влияет на отображение компонента).
Когда вы работаете со списком элементов и у каждого элемента должно быть свое состояние, лучше всего выносить это в отдельный компонент.
Ниже, пример ваших компонентов, которые получают список стран и отображают детальную информацию по стране.
const ShowCountry = ({ country }) => {
const [state, setState] = useState({});
const [isShow, setShow] = useState(false);
const getInfo = () => {
setShow((s) => !s);
if (state.capital) return;
const baseUrlItem = `https://studies.cs.helsinki.fi/restcountries/api/name/${country}`;
fetch(baseUrlItem)
.then((res) => res.json())
.then((res) => {
res.languages = Object.values(res.languages).join(", ");
setState(res);
return res;
});
};
return (
<div>
<b>{country}</b>{" "}
<button onClick={getInfo}>
{isShow ? "Скрыть инфо" : "Показать подробнее"}
</button>
{isShow && state.capital && (
<div>
<h1>{state.country}</h1>
<p>{state.capital[0]}</p>
<p>{state.area}</p>
<p>{state.languages}</p>
<div style={{ border: "1px solid black", width: "fit-content" }}>
<img src={state.flags["png"]} />
</div>
</div>
)}
</div>
);
};
const App = () => {
const [countries, setCountries] = useState([]);
const [findCountrieValue, setFindCountrieValue] = useState("");
const findCountrie = async (event) => {
fetch("https://studies.cs.helsinki.fi/restcountries/api/all")
.then((response) => response.json())
.then((response) => {
setCountries(
response
.map((c) => c.name.common)
.filter(
(c) =>
c.toUpperCase().indexOf(findCountrieValue.toUpperCase()) > -1
)
);
});
};
return (
<div>
<input onInput={(e) => setFindCountrieValue(e.currentTarget.value)} />
<button onClick={findCountrie}>find</button>
{countries.map((country) => (
<ShowCountry key={country} country={country} />
))}
</div>
);
};
Что изменилось:
Поиск стран осуществляется по подстроке
c.toUpperCase().indexOf(findCountrieValue.toUpperCase())
Полученный список отрисовывается и каждое значение теперь живет само по себе в компоненте ShowCountry
{countries.map((country) => (
<ShowCountry key={country} country={country} />
))}
При нажатии на кнопку, запрашивается дополнительная информация по стране. При повторном нажатии информация скрывается, если нажать еще раз, т.к. данные уже есть, то информация просто отображается и повторного запроса данных не происходит.
Тестировал в песочнице codesandbox.io