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

如何提前加载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;

关键修改说明

  1. Context新增加载状态:通过isLoading明确区分“正在加载”和“未登录”两种状态,避免依赖user === null的模糊判断。
  2. 监听Context变化:利用useEffect监听isContextLoading和context的变化,确保用户数据加载完成后再执行登录状态验证。
  3. 修正初始状态:将isLogin初始值设为false,避免首次渲染时误判为已登录。
  4. 添加循环key:给map生成的元素添加key属性,符合React渲染规范。

内容的提问来源于stack exchange,提问作者learnerOp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 23:10:33