#javascript #reactjs #react-hooks
Вопрос:
Я создаю веб — приложение, которое использует общедоступный API и отображает эти данные в таблице. Но в половине случаев, когда я нажимаю кнопку «Нажмите, чтобы отобразить самую горячую планету», она ломается и показывает эту ошибку. Как можно решить эту проблему, не беспокоясь о том, что она сломается? Я хочу развернуть его как портфельный проект. Это прекрасно работает, когда тур не активен.
TypeError: hotPlanet is undefined
103 | {popup amp;amp; <Popup
104 | boilerPlate='The Hottest Planet is : '
> 105 | content={hotPlanet.PlanetIdentifier}
| ^ 106 | handleClose={togglePopup}
107 | />}
108 | <Table className="table-component" data={planets} columns={COLUMNS}></Table>
Homepage.js файл
import React,{useState,useEffect,useContext} from 'react'
import axios from "axios";
import Table from '../components/Table'
import {COLUMNS} from '../components/columns';
import './styles.css';
import Tour from 'reactour'
import getTopN from '../components/FindMax';
import getMax from '../components/WayTwo';
import Popup from '../components/Popup';
const steps = [
{
selector: '.Welcome',
content: 'This is my solution to take home assignment',
},
{
selector: '.reload-btn',
content: ' This buttons reloads the data if need be'
},
{
selector: '.btn-tutorial',
content: 'These buttons right next to table heading help sort the data. If the button is white it is unsorted, if the button in red then it is sorted in descending order. If it is green it is sorted in ascending order'
},
{
selector: '.sol1',
content: 'To find out about orphan planets Go to the dropdown next to Stellar Formation column and select 3'
},
{
selector: '.sol2',
content: 'To group the planets by the year they where discovered please type in the input box next to " Discovered In"'
},
{
selector: '.sol3',
content: 'To Find the Hottest star Press the dot button next to Host Temperature column'
}
];
export default function HomePage() {
const [planets,setPlanets] = useState([]);
const [reload,setReload] = useState(0);
const [isTourOpen, setIsTourOpen] = useState(true);
const [popup,setPopup] = useState(false);
const [hotPlanet,setHotPlanet] = useState({})
function getData()
{
const url = "https://gist.githubusercontent.com/joelbirchler/66cf8045fcbb6515557347c05d789b4a/raw/9a196385b44d4288431eef74896c0512bad3defe/exoplanets";
axios.get(url).then((response) => setPlanets(response.data));
}
function newHotPlanet()
{
var maxPpg = getMax(planets, "HostStarTempK");
setHotPlanet(maxPpg);
}
useEffect(()=>
{
getData();
console.log(planets)
newHotPlanet();
},[reload])
useEffect(()=>
{
getData();
console.log(planets)
newHotPlanet();
},[])
function handleReload(e)
{
getData();
setReload(reload 1)
}
const [isOpen, setIsOpen] = useState(false);
const togglePopup = () => {
setPopup(!popup);
}
function handlePopup(e)
{
e.preventDefault();
getData();
var maxPpg = getMax(planets, "HostStarTempK");
setHotPlanet(maxPpg);
console.log(hotPlanet);
setPopup(true);
}
return (
<div>
<h1 className = "Welcome">Planet Portal</h1>
<Tour
steps={steps}
isOpen={isTourOpen}
onRequestClose={() => setIsTourOpen(false)}
/>
<button className="reload-btn" onClick={handleReload}> Reload Data</button>
<button onClick={togglePopup} className="sol3"> Click to Display Hottest Planet</button>
{popup amp;amp; <Popup
boilerPlate='The Hottest Planet is : '
content={hotPlanet.PlanetIdentifier}
handleClose={togglePopup}
/>}
<Table className="table-component" data={planets} columns={COLUMNS}></Table>
</div>
)
}
Он отлично работает, когда я не использую Tour из react tour
Ответ №1:
Причина здесь в том, что вы неправильно обрабатываете асинхронную загрузку данных:
function handlePopup(e)
{
e.preventDefault();
getData();
var maxPpg = getMax(planets, "HostStarTempK");
setHotPlanet(maxPpg);
console.log(hotPlanet);
setPopup(true);
}
где линия getData()
только начинает загружаться. Данные еще не получены и определенно не установлены planets
. И, не дожидаясь следующей очереди var maxPpg = ...
, пытается найти лучший товар. И функция getMax()
возвращает undefined
пустой массив из planets
. Вот как hotPlanet
это происходит undefined
, а затем происходит сбой при повторном рендеринге.
Самым простым исправлением было бы дождаться получения данных. Сначала нам нужно вернуться Promise
из getData
:
function getData()
{
const url = // .....;
return axios.get(url).then((response) => setPlanets(response.data));
}
И далее мы ждем, когда он будет закончен в handlePopup
:
async function handlePopup(e)
{
e.preventDefault();
await getData();
var maxPpg = getMax(planets, "HostStarTempK");
setHotPlanet(maxPpg);
setPopup(true);
}
Даже для других подходов, которые нам все еще нужны await getData()
, всплывающее окно никогда не будет отображаться, пока hotPlanet
оно еще не рассчитано.
Загляните в раздел AJAX и API.
Но самый простой подход — не самый лучший. На мой взгляд, мы всегда хотим, чтобы наша планета была актуальной. Поэтому вместо того, чтобы вычислять его по щелчку мыши, мы всегда можем пересчитать(решение № 2) в зависимости от любого изменения в planets
:
useEffect(() => {
setHotPlanet(getMax(planets, "HostStartTempK"));
}, [planets]);
Но есть еще лучший подход(решение № 3). Данные, которые вычисляются другим состоянием, называются «производными». Возможно, вам на самом деле не нужно хранить hotPlanet
отдельную переменную, а просто пересчитать значение:
{popup amp;amp; <Popup
boilerPlate='The Hottest Planet is : '
content={getMax(planets, "HostStarTempK").PlanetIdentifier}
handleClose={togglePopup}
/>}
Да, при сложной логике поиска или огромных массивах(я считаю, что список из 100 элементов имеет небольшой размер) могут возникнуть проблемы с производительностью. В этом случае мы можем захотеть hotPlanet
запомнить (решение № 4), которое все равно будет более подходящим, чем наличие дополнительного состояния:
const hotPlannet = useMemo(() => getMax(planets, "HostStarTempK"), [planets]);