You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

核心问题分析

  1. 初始渲染数据未加载:组件挂载时questionsAndAnswers初始为空数组,此时qna[questionNum]为undefined,但Question组件直接访问currentQ.answer,触发Cannot read property 'answer' of undefined报错。
  2. 生命周期函数未做数据校验:componentDidUpdate和handlePressedLetter中直接访问数组元素的answer属性,未判断数据是否已加载完成,导致异步加载期间触发报错。
  3. 异步状态更新判断逻辑错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 07:01:55