如何提前加载React useContext中的用户详情并解决加载延迟问题
解决方案:解决useContext用户详情加载延迟导致的登录状态误判问题
问题核心在于两点:一是Context的用户数据通过异步请求获取,组件首次渲染时context为null,此时执行checkContext会直接把isLogin设为false;二是组件挂载后未监听context变化,导致请求完成后不会重新验证登录状态。
步骤1:优化UserContext,增加加载状态
给Context添加加载状态,让子组件能明确感知用户数据的请求状态。可以将用户数据和加载状态打包到同一个Context中,避免新增额外Context:
import { React, createContext, useState, useEffect } from "react"; const UserContext = createContext(); const DispatchUserContext = createContext(); const UserContextProvider = ({ children }) => { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); // 新增加载状态 useEffect(() => { const fetchUser = async () => { const token = "Bearer " + localStorage.getItem("jwt"); try { const res = await fetch("http://127.0.0.1:9000/OTS/user/profile", { method: "GET", headers: { "Content-Type": "application/json", authorization: token, }, }); const data = await res.json(); if (data.status !== "success") { setUser(null); } else { setUser(data.data); } } catch (err) { console.log(err); setUser(null); } finally { setIsLoading(false); // 请求完成后无论结果如何都结束加载 } } fetchUser(); }, []) // 将用户数据和加载状态一起传入Provider return ( <UserContext.Provider value={{ user, isLoading }}> <DispatchUserContext.Provider value={setUser}> {children} </DispatchUserContext.Provider> </UserContext.Provider> ) } export { UserContext, DispatchUserContext, UserContextProvider };
步骤2:修改MyOrder组件逻辑
监听Context的状态变化,等用户数据加载完成后再验证登录状态:
import { useContext, useState, useEffect } from 'react'; import { UserContext } from './UserContext'; import { Grid, Skeleton, Accordion, AccordionSummary, AccordionDetails, Typography, TableContainer, Table, TableHead, TableRow, TableCell } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import AlertDialog from './AlertDialog'; const MyOrder = () => { const { user: context, isLoading: isContextLoading } = useContext(UserContext); // 解构获取用户数据和加载状态 const [isLogin, setIsLogin] = useState(false); const [isLoading, setIsLoading] = useState(true); const [orders, setOrders] = useState([ { OrderedItems: [], contactNumber: "", deliveryCharge: 0, mealType: "", orderBy: "", placedAt: "", shippingAddress: "", totalPrice: 0, }, ]); // 验证登录状态的逻辑 const checkContext = () => { if (context) { setIsLogin(true); // 这里可以添加获取用户订单的逻辑,比如调用接口拉取当前用户的订单列表 // fetchOrders(context.userId).then(data => setOrders(data)); } else { setIsLogin(false); } setIsLoading(false); }; // 当Context加载完成后执行验证逻辑 useEffect(() => { if (!isContextLoading) { checkContext(); } }, [isContextLoading, context]); // 依赖Context的加载状态和用户数据 return ( <> {isLoading ? ( <Grid container align="center" sx={{ marginTop: "30vh" }}> <Skeleton variant="rounded" width={210} height={200} /> </Grid> ) : ( <> {isLogin ? ( <div> <h2 style={{ marginTop: 100, textAlign: "center" }}>我的订单</h2> <Accordion> <AccordionSummary aria-controls="panel1bh-content" id="panel1bh-header" > <Typography sx={{ width: "5%", flexShrink: 0, textAlign: "left" }}>序号</Typography> <Typography sx={{ width: "10%", flexShrink: 0 }}>餐品类型</Typography> <Typography sx={{ width: "20%", flexShrink: 0 }}>下单日期</Typography> <Typography sx={{ width: "28%", flexShrink: 0, textAlign: "center" }}>收货地址</Typography> <Typography sx={{ width: "10%", flexShrink: 0, textAlign: "center" }}>配送费</Typography> <Typography sx={{ width: "10%", flexShrink: 0, textAlign: "center" }}>总金额</Typography> </AccordionSummary> </Accordion> {orders.map((order, id) => ( <div key={id}> <Accordion> <AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="panel1bh-content" id="panel1bh-header" > <Typography sx={{ width: "5%", flexShrink: 0 }}>{id + 1}</Typography> <Typography sx={{ width: "10%", flexShrink: 0 }}>{order.mealType}</Typography> <Typography sx={{ width: "20%", flexShrink: 0 }}>{order.placedAt.substring(0, 10)}</Typography> <Typography sx={{ width: "30%", flexShrink: 0 }}>{order.shippingAddress}</Typography> <Typography sx={{ width: "10%", flexShrink: 0, textAlign: "center" }}>{order.deliveryCharge}</Typography> <Typography sx={{ width: "10%", flexShrink: 0, textAlign: "center" }}>{order.totalPrice}</Typography> </AccordionSummary> <AccordionDetails> <TableContainer> <Table sx={{ minWidth: 500 }} aria-label="订单详情表"> <TableHead> <TableRow> <TableCell>序号</TableCell> <TableCell>商品名称</TableCell> <TableCell>数量</TableCell> <TableCell>单价</TableCell> <TableCell>小计</TableCell> </TableRow> </TableHead> <TableBody> {order.OrderedItems.map((item, itemId) => ( <TableRow key={itemId} sx={{ "&:last-child td, &:last-child th": { border: 0 } }} > <TableCell>{itemId + 1}</TableCell> <TableCell>{item.ItemName}</TableCell> <TableCell>{item.Quantity}</TableCell> <TableCell>{item.ItemPrice}</TableCell> <TableCell>{item.ItemPrice * item.Quantity}</TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> </AccordionDetails> </Accordion> </div> ))} </div> ) : ( <AlertDialog open={!isLogin} /> )} </> )} </> ); }; export default MyOrder;
关键修改说明
- Context新增加载状态:通过
isLoading明确区分“正在加载”和“未登录”两种状态,避免依赖user === null的模糊判断。 - 监听Context变化:利用
useEffect监听isContextLoading和context的变化,确保用户数据加载完成后再执行登录状态验证。 - 修正初始状态:将
isLogin初始值设为false,避免首次渲染时误判为已登录。 - 添加循环key:给
map生成的元素添加key属性,符合React渲染规范。
内容的提问来源于stack exchange,提问作者learnerOp
相关产品推荐
相关产品推荐

