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

Firefox下React生产环境应用根元素无法渲染的异常排查

Firefox首次访问React生产应用时抛出TypeError,刷新后恢复正常

我有一个构建为生产环境的React应用,由Node/Express服务器提供服务。本地启动服务器及部署到Heroku、Render.com后,Chrome和Edge访问均正常无报错,但Firefox首次访问时无法填充根元素,抛出如下异常:

TypeError: wi.get(...) is undefined
    ts Header.jsx:36
    React 7
    C scheduler.production.min.js:13
    T scheduler.production.min.js:14
    813 scheduler.production.min.js:14
    Webpack 12
react-dom.production.min.js:189:29
    React 9
    C scheduler.production.min.js:13
    T scheduler.production.min.js:14
    (Async: EventHandlerNonNull)
    813 scheduler.production.min.js:14
    Webpack 12

错误指向的代码位置无实际代码:

const availableLanguages = [
  {
    code: "he",
    name: "עברית",
    country_code: "il",
    dir: "rtl",
  },
  {
    code: "en",
    name: "English",
    country_code: "gb",
  },
  {
    code: "de",
    name: "Deutsch",
                             ^---- 错误指向此处,但无代码!
    country_code: "de",
  },
];

该问题在本地、Heroku、Render.com三个环境,三台不同设备上均能复现,刷新Firefox后页面显示正常。完整Header.jsx代码如下:

import { useState, useRef, useEffect } from "react";
import i18next from "i18next";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import useOutsideClick from "../hooks/useOutsideClick";
import useAuth from "../hooks/useAuth";
import Anchor from "./Anchor";
import "./Header.css";
import claryNextLogo from "../images/ClaryNext2.png";
import cookies from "js-cookie";

import { config } from "../Environment";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { solid } from "@fortawesome/fontawesome-svg-core/import.macro";
import { useContext } from "react";
import { WSContext } from "../App";
import { useEffectOnce } from "../hooks/useEffectOnce";
import { distributionService } from "../services/distributionService";
import MessageBox from "./MessageBox";
import useRetryFetch from "../hooks/useRetryFetch";

const availableLanguages = [
  {
    code: "he",
    name: "עברית",
    country_code: "il",
    dir: "rtl",
  },
  {
    code: "en",
    name: "English",
    country_code: "gb",
  },
  {
    code: "de",
    name: "Deutsch",         
    country_code: "de",
  },
];

function Header() {
  const currentLanguageCode = cookies.get("i18next").split("-")[0] || "en";
  const currentLanguage = availableLanguages.find(
    (l) => l.code === currentLanguageCode
  );

  const { auth, setAuth } = useAuth();
  const navigate = useNavigate();

  const dropdownRef = useRef(null);

  const retryFetch = useRetryFetch();

  const [showLanguageDropdown, setShowLanguageDropdown] = useState(false);
  const [showMessageBox, setShowMessageBox] = useState(false);
  const [msgBoxTitle, setMsgBoxTitle] = useState("");
  const [msgBoxButtons, setMsgBoxButtons] = useState({});
  const [msgBoxInputs, setMsgBoxInputs] = useState([]);
  const [msgBoxId, setMsgBoxId] = useState("");
  const [msgBoxMoreJSX, setMsgBoxMoreJSX] = useState(null);

  const [chatRequestUser, setChatRequestUser] = useState("");
  const [chatRequestProcessId, setChatRequestProcessId] = useState("");
  const [chatRequestRoomId, setChatRequestRoomId] = useState(0);

  const { t } = useTranslation();

  const { socket } = useContext(WSContext);

  const openMessageBox = (title, id, buttons, inputs, moreJSX) => {
    setMsgBoxTitle(title);
    setMsgBoxId(id);
    setMsgBoxButtons(buttons);
    setMsgBoxInputs(inputs);
    setMsgBoxMoreJSX(moreJSX);
    setShowMessageBox(true);
  };

  const onButton = async (result) => {
    console.log("MessageBox button was clicked");
    console.dir(result);

    if (result.btnId === "yes") {
      // chat was accepted, change to ShowFullLog, join room and notify user
      socket.send(
        JSON.stringify({
          command: "acceptchat",
          payload: { email: chatRequestUser, roomId: chatRequestRoomId },
        })
      );

      // collect necessary process log information
      let response = await retryFetch(
        `${config.API_BASE}/api/processes/${chatRequestProcessId}`
      );
      let process = await response.json();

      let stateToPass = {
        processId: chatRequestProcessId,
        presName: process.prescriptionName,
        presHistoryId: process.fiPrescriptionHistory._id,
        autochat: true,
      };
      if (process.fiExpert?.email === auth.email) {
        stateToPass.userEmail = process.fiInitiator?.email;
        navigate("/fulllog", { state: stateToPass });
      } else {
        stateToPass.expertEmail = process.fiExpert?.email;
        navigate("/userlog", { state: stateToPass });
      }
    } else {
      // chat was refused, send message to requesting user
      socket.send(
        JSON.stringify({
          command: "refusechat",
          payload: {
            email: chatRequestUser,
            roomId: chatRequestRoomId,
          },
        })
      );
    }
  };

  useEffect(() => {
    document.body.dir = currentLanguage?.dir || "ltr";
  }, [currentLanguage]);

  useEffectOnce(() => {
    let messageUnsubscribe = distributionService
      .getMessage()
      .subscribe((msg) => {
        console.log("Header incoming message");

        switch (msg.command) {
          case "requestchat":
            setChatRequestUser(msg.payload.email);
            setChatRequestProcessId(msg.payload.processId);
            setChatRequestRoomId(msg.payload.roomId);
            openMessageBox(
              t("msg_chat_requested", {
                user: msg.payload.email,
                processId: msg.payload.processId,
              }),
              "requestchat",
              [
                { id: "yes", text: t("yes") },
                { id: "no", text: t("no") },
              ],
              [],
              ""
            );
            break;
        }
      });

    return () => {
      // cleanup subscription
      messageUnsubscribe.unsubscribe();
    };
  }, []);

  const login = () => {
    navigate("/login");
  };

  const logout = async () => {
    let response = await fetch(config.API_BASE + "/logout", {
      credentials: "include",
    });

    if (!response.ok) alert(t("msg_error_logout"));

    let result = await response.json();
    console.log(result);
    setAuth({});

    socket.send(
      JSON.stringify({
        command: "logout",
        payload: undefined,
      })
    );

    navigate("/");
  };

  // const register = () => {
  //   navigate("/register");
  // };

  const showPrescriptions = () => {
    //navigate("/design");
    navigate("/prescriptionlist");
  };

  const goHome = () => {
    navigate("/");
  };

  const openMenu = () => {
    setShowLanguageDropdown(true);
  };

  const closeMenu = () => {
    setShowLanguageDropdown(false);
  };

  const selectLanguage = (code) => {
    i18next.changeLanguage(code);
    setShowLanguageDropdown(false);
  };

  useOutsideClick(dropdownRef, closeMenu);

  return (
    <>
      <div className="header-menu">
        <div className="title" onClick={goHome}>
          <img src={claryNextLogo} alt="ClaryNext logo" width="90" />
        </div>
        {!auth?.email ? (
          <>
            <div className="notonmobile">
              <div>
                <Anchor
                  onClick={showPrescriptions}
                  text={t("example_prescriptions")}
                />
              </div>
            </div>
            <div className="rightflex notonmobile">
              <div className="rightMargin">
                <span>&nbsp;</span>
              </div>
              <button onClick={login}>{t("login")}</button>
              <span className="leftMargin bigger">
                <FontAwesomeIcon
                  onClick={() => openMenu()}
                  icon={solid("globe")}
                />
              </span>
            </div>
          </>
        ) : (
          <>
            <div className="rightMargin notonmobile">
              <Anchor
                onClick={showPrescriptions}
                text={t("prescriptions_and_processes")}
              />
            </div>
            <div className="rightflex notonmobile">
              <div className="rightMargin">
                <Anchor onClick={logout} text={t("logout")} />
              </div>
              <span className="smaller">{auth.email}</span>
              <span className="leftMargin bigger">
                <FontAwesomeIcon
                  onClick={() => openMenu()}
                  icon={solid("globe")}
                />
              </span>
            </div>
          </>
        )}
      </div>
      {showMessageBox ? (
        <MessageBox
          onButton={onButton}
          buttons={msgBoxButtons}
          text={msgBoxTitle}
          inputs={msgBoxInputs}
          id={msgBoxId}
          moreJSX={msgBoxMoreJSX}
          onClose={() => setShowMessageBox(false)}
        />
      ) : (
        ""
      )}
      {showLanguageDropdown ? (
        <div className="language_dropdown" ref={dropdownRef}>
          {availableLanguages.map((lang, idx) => (
            <p key={idx} onClick={() => selectLanguage(lang.code)}>
              <span
                className={`flag-icon flag-icon-${lang.country_code}`}
              ></span>
              {lang.name}
            </p>
          ))}
        </div>
      ) : (
        ""
      )}
    </>
  );
}

export default Header;

请问有人知道这是什么原因吗?


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 04:25:29