React类组件中API数据获取报错问题排查与解决
问题排查:React类组件从SpringBoot获取数据渲染报错
问题背景
在React类组件中替换假数据,改用外部SpringBoot数据源获取数据时出现不明原因报错,控制台打印数据显示undefined,但使用函数组件的useEffect调用相同URL能正常获取数据,报错集中在Question子组件中。
SpringBoot API返回格式
[{"id":1,"question":"What is the capital of France","answer":"PARIS"},{"id":2,"question":"What is the capital of India","answer":"DELHI"},{"id":3,"question":"Excellent! Congratulations!","answer":"You Survived"}]
Game类组件代码
import React, { Component, useState } from 'react'; import Question from '../Question/Question'; import Keyboard from '../Keyboard/Keyboard'; import Puppet from '../Puppet/Puppet'; import GameStatus from '../GameStatus/GameStatus'; // import { questionsAndAnswers } from '../../gamedata'; import Confetti from 'react-confetti'; import './Game.css'; export class Game extends Component { constructor(props) { super(props); this.state = { questionsAndAnswers:[], triggerConfetti: false, gameCompleted: false, gameAlive: true, pressedLettersArray: [], currOp: 0, maxOps: 6, questionNumber: 0, keys: [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'Ñ', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ] }; } async componentDidMount() { fetch("http://localhost:8080/qna/all") .then(result=>result.json()) .then((result)=>{ this.setState({questionsAndAnswers: result}) console.log("Recieved Details") }) // const response = await fetch("http://localhost:8080/qna/all"); // const body = await response.json(); // console.log(body); // this.setState({questionsAndAnswers: body}); if (this.state.gameCompleted === true) { this.setState({ triggerConfetti: true }); } } handlePressedLetter = letter => { const pressedLettersArray = [...this.state.pressedLettersArray] || []; const word = this.state.questionsAndAnswers[this.state.questionNumber].answer; if (word.includes(letter)) { this.setState({ pressedLettersArray: [...pressedLettersArray, letter] }); } else { this.setState({ currOp: this.state.currOp + 1 }); if (this.state.currOp >= this.state.maxOps) { this.setState({ gameAlive: false }); } } }; resetGame = () => { this.setState({ triggerConfetti: false, gameCompleted: false, gameAlive: true, currOp: 0, questionNumber: 0, pressedLettersArray: [] }); }; componentDidUpdate = () => { const wordLettersArr = this.state.questionsAndAnswers[this.state.questionNumber].answer.split(''); const keysPressedArr = this.state.pressedLettersArray; const complete = wordLettersArr.every(letter => keysPressedArr.includes(letter)); if (complete) { if (this.state.questionNumber < this.state.questionsAndAnswers.length - 2) { this.setState({ pressedLettersArray: [], currOp: 0, questionNumber: this.state.questionNumber + 1 }); } else if ( this.state.questionNumber === this.state.questionsAndAnswers.length - 2 && this.state.gameCompleted === false ) { this.setState({ gameCompleted: true, currOp: 0, pressedLettersArray: this.state.questionsAndAnswers[this.state.questionNumber + 1].answer.split(''), questionNumber: this.state.questionNumber + 1 }); } } if (this.state.gameCompleted === true && this.state.triggerConfetti === false) { this.setState({ triggerConfetti: true }); } }; render() { const word = (this.state.questionsAndAnswers[this.state.questionNumber] && this.state.questionsAndAnswers[this.state.questionNumber].answer); return ( <div className="game"> {this.state.triggerConfetti ? ( <Confetti height={document.querySelector('.game').offsetHeight} width={document.querySelector('.game').offsetWidth} /> ) : null} <h2>Welcome</h2> <Puppet questionNumber={this.state.questionNumber} currOp={this.state.currOp} gameAlive={this.state.gameAlive} qna={this.state.questionsAndAnswers} /> <Question qna={this.state.questionsAndAnswers} questionNum={this.state.questionNumber} pressedLettersArray={this.state.pressedLettersArray} gameAlive={this.state.gameAlive} /> <GameStatus qna={this.state.questionsAndAnswers} gameAlive={this.state.gameAlive} resetGame={this.resetGame} questionNumber={this.state.questionNumber} gameCompleted={this.state.gameCompleted} /> <Keyboard keys={this.state.keys} handlePressedLetter={this.handlePressedLetter} word={word} gameAlive={this.state.gameAlive} gameCompleted={this.state.gameCompleted} /> </div> ); } } export default Game;
Question子组件代码
import React from 'react' import { useTransition, animated } from 'react-spring' import Letter from '../Letter/Letter' import './Question.css' const Question = ({gameAlive, qna, questionNum, pressedLettersArray}) => { const currentQ = qna[questionNum]; const transitions = useTransition(currentQ, currentQ => currentQ.answer, { from: { opacity: 0, transform: 'translateX(100%)' }, enter: { opacity: 1, transform: 'translateX(0px)' }, leave: { opacity: 0, transform: 'translateX(-100%)' } }) return ( transitions.map(({ item, key, props }) => ( <animated.div className={gameAlive ? "question" : "question gray"} key={key} style={props}> <p>{item.question}</p> <div className="guessing-word"> {item.answer.split("").map((singleLetter, index) => ( <Letter key={`${index}${singleLetter}`} singleLetter={singleLetter} pressedLettersArray={pressedLettersArray} /> ))} </div> </animated.div> ) ) ) } export default Question
核心问题分析
- 初始渲染数据未加载:组件挂载时
questionsAndAnswers初始为空数组,此时qna[questionNum]为undefined,但Question组件直接访问currentQ.answer,触发Cannot read property 'answer' of undefined报错。 - 生命周期函数未做数据校验:
componentDidUpdate和handlePressedLetter中直接访问数组元素的answer属性,未判断数据是否已加载完成,导致异步加载期间触发报错。 - 异步状态更新判断逻辑错误:
handlePressedLetter中直接用更新前的currOp判断是否结束游戏,由于React状态更新是异步的,判断逻辑会失效。
修复方案
1. 给Question组件添加加载判断
修改Question组件,先校验数据是否存在:
const Question = ({gameAlive, qna, questionNum, pressedLettersArray}) => { const currentQ = qna[questionNum]; // 数据未加载时返回加载提示 if (!currentQ) { return <div className="question">Loading...</div>; } const transitions = useTransition(currentQ, currentQ => currentQ.answer, { from: { opacity: 0, transform: 'translateX(100%)' }, enter: { opacity: 1, transform: 'translateX(0px)' }, leave: { opacity: 0, transform: 'translateX(-100%)' } }) return ( transitions.map(({ item, key, props }) => ( <animated.div className={gameAlive ? "question" : "question gray"} key={key} style={props}> <p>{item.question}</p> <div className="guessing-word"> {item.answer.split("").map((singleLetter, index) => ( <Letter key={`${index}${singleLetter}`} singleLetter={singleLetter} pressedLettersArray={pressedLettersArray} /> ))} </div> </animated.div> ) ) ) }
2. 修复componentDidUpdate中的数据访问
添加数据存在性校验:
componentDidUpdate = () => { const currentQ = this.state.questionsAndAnswers[this.state.questionNumber]; if (!currentQ) return; const wordLettersArr = currentQ.answer.split(''); const keysPressedArr = this.state.pressedLettersArray; const complete = wordLettersArr.every(letter => keysPressedArr.includes(letter)); if (complete) { if (this.state.questionNumber < this.state.questionsAndAnswers.length - 2) { this.setState({ pressedLettersArray: [], currOp: 0, questionNumber: this.state.questionNumber + 1 }); } else if ( this.state.questionNumber === this.state.questionsAndAnswers.length - 2 && this.state.gameCompleted === false ) { this.setState({ gameCompleted: true, currOp: 0, pressedLettersArray: this.state.questionsAndAnswers[this.state.questionNumber + 1].answer.split(''), questionNumber: this.state.questionNumber + 1 }); } } if (this.state.gameCompleted === true && this.state.triggerConfetti === false) { this.setState({ triggerConfetti: true }); } };
3. 修复handlePressedLetter中的逻辑
添加数据校验并修正异步状态判断:
handlePressedLetter = letter => { const currentQ = this.state.questionsAndAnswers[this.state.questionNumber]; if (!currentQ) return; const pressedLettersArray = [...this.state.pressedLettersArray] || []; const word = currentQ.answer; if (word.includes(letter)) { this.setState({ pressedLettersArray: [...pressedLettersArray, letter] }); } else { this.setState(prevState => ({ currOp: prevState.currOp + 1 }), () => { // 用回调获取更新后的状态值 if (this.state.currOp >= this.state.maxOps) { this.setState({ gameAlive: false }); } }); } };
4. 优化componentDidMount的异步逻辑
移除无效判断并添加错误处理:
async componentDidMount() { try { const response = await fetch("http://localhost:8080/qna/all"); const body = await response.json(); this.setState({questionsAndAnswers: body}); } catch (error) { console.error('Failed to fetch data:', error); } }
内容的提问来源于stack exchange,提问作者Saikat Chattoraj
相关产品推荐
相关产品推荐

