React中如何确保JSX仅在state变量填充JSON响应后渲染?
问题解决思路与代码修复
核心问题分析
- 误用
useMemo处理API请求:useMemo是用来缓存计算结果的,不是处理API请求这类副作用的正确工具,应该用useEffect。 - 初始状态设置错误:你把
tutorDetails初始化为数组[],但API返回的是对象,直接访问tutorDetails.subjects会因为数组没有该属性报错。 - 未处理异步加载状态:数据请求是异步的,JSX会先执行,此时
tutorDetails还没被赋值,直接访问内部属性会抛出错误。
解决方案
1. 替换useMemo为useEffect
用useEffect处理API请求,同时把props.tutorID加入依赖数组,确保导师ID变化时重新请求数据。
2. 修正初始状态
将tutorDetails初始值设为null,方便判断数据是否加载完成。
3. 条件渲染JSX
在渲染tutorDetails相关内容前,先判断数据是否存在,避免报错;也可以添加加载状态提示。
修复后的完整代码
import React, { useState, useEffect } from "react"; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import Slide from '@mui/material/Slide'; import axios from 'axios'; function TutorCard({ tutor_id, first_name, last_name, grade, subject }) { return ( <div className="md:flex bg-white shadow text-gray-800 my-4 py-4 px-10 rounded-md items-center justify-between hover:bg-gray-300"> <p className="font text-md" style={{ color: 'black', textAlign: "center" }}>{tutor_id}</p> <p className="font text-md" style={{ color: 'black', textAlign: "center" }}>{first_name}</p> <p className="font text-md" style={{ color: 'black', textAlign: "center" }}>{last_name}</p> <p style={{ color: 'black', textAlign: "center" }}>{grade}</p> <p style={{ color: 'black', textAlign: "center" }}>{subject}</p> <AlertDialogSlide tutorID={tutor_id} firstName={first_name} lastName={last_name} grade={grade} subject={subject} /> </div> ) } const Transition = React.forwardRef(function Transition(props, ref) { return <Slide direction="up" ref={ref} {...props} />; }); function AlertDialogSlide(props) { const [open, setOpen] = React.useState(false); const [tutorDetails, setTutorDetails] = useState(null); const [isLoading, setIsLoading] = useState(false); const handleClickOpen = () => { setOpen(true); }; const handleClose = () => { setOpen(false); }; // 用useEffect替代useMemo处理API请求 useEffect(() => { // 弹窗打开时才请求数据,避免不必要的请求 if (!open) return; const retrieveData = async () => { setIsLoading(true); try { const resp = await axios.get('API call' + props.tutorID); setTutorDetails(resp.data); } catch (error) { console.error('获取导师详情失败:', error); } finally { setIsLoading(false); } } retrieveData(); }, [open, props.tutorID]); return ( <div> <Button variant="outlined" onClick={handleClickOpen}> {'>'} </Button> <Dialog open={open} TransitionComponent={Transition} keepMounted onClose={handleClose} aria-describedby="alert-dialog-slide-description" fullWidth={true} > <DialogTitle>{props.firstName + " " + props.lastName}</DialogTitle> <DialogContent> <DialogContentText> <strong>Level: </strong> {props.grade} <br /> <strong>Subject: </strong> {props.subject} <br /> {/* 条件渲染:先判断加载状态和数据是否存在 */} {isLoading ? ( <span>加载中...</span> ) : tutorDetails ? ( <> <strong>Alternative Subject: </strong> {tutorDetails.subjects?.[0] || '无'} <br /> {/* 若要渲染所有备选科目,可取消下面注释 */} {/* <div> {tutorDetails.subjects?.map((sub, index) => ( <div key={index}> <strong>Alternative Subject {index + 1}: </strong> {sub} <br /> </div> ))} </div> */} </> ) : ( <span>暂无更多信息</span> )} </DialogContentText> </DialogContent> <DialogActions> <Button onClick={handleClose}>Close</Button> </DialogActions> </Dialog> </div> ); } export default TutorCard;
关键优化点说明
useEffect处理异步请求:符合React副作用处理规范,依赖数组确保请求时机正确。- 加载状态提示:提升用户体验,避免数据未加载时的空白或错误。
- 可选链操作符
?.:即使tutorDetails.subjects不存在也不会报错,增强代码健壮性。 - 条件渲染:确保只有数据加载完成后才渲染对应内容,解决JSX提前执行导致的无数据问题。
内容的提问来源于stack exchange,提问作者BaMdan
相关产品推荐
相关产品推荐

