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

React中ToastContainer无法作为JSX组件使用的报错问题

解决react-toastify ToastContainer JSX组件类型报错问题

问题描述

开发React OTP组件时,使用react-toastify的ToastContainer组件出现类型报错。

错误信息

ToastContainer无法作为JSX组件使用。其返回类型'ReactElement<any, string | JSXElementConstructor> | null'不是有效的JSX元素。'ReactElement<any, string | JSXElementConstructor>'类型无法赋值给'Element'类型,key属性类型不兼容。

相关代码

import axios from "axios";
import React, { useState, useEffect } from "react";
import { ToastContainer, toast } from "react-toastify";
import loaderIcon from "../../assets/images/loader.gif";
import "react-toastify/dist/ReactToastify.css";
import { IOtpProps } from "./otp.types";

export function Otp({ showOtpForm, email, setIsLoggedIn }: IOtpProps) {
  const [otp, setOtp] = useState("");
  const [successMsg, setSuccessMsg] = useState("");
  const [otpExpired, setOtpExpired] = useState(false);
  const [incorrectAttempts, setIncorrectAttempts] = useState(0);
  const [disableResend, setDisableResend] = useState(true);
  const [timer, setTimer] = useState(30);
  const [buttonOpacity, setButtonOpacity] = useState(1);
  const [isLoading, setIsLoading] = useState(false);
  const [loginError, setLoginError] = useState("");
  const [errorMessage, setErrorMessage] = useState("");

  const serverUrl = "http://localhost:5000/api";

  useEffect(() => {
    if (showOtpForm) {
      // Display toast message for 5 seconds
      toast.success("Otp has been send to your Email", {
        autoClose: 5000,
        style: {
          fontFamily: "Noto Sans",
          fontSize: "15px",
          fontWeight: "600",
          borderRadius: "14px",
        },
      });
    }
  }, [showOtpForm]);

  const handleOtpChange = (e: any) => {
    setOtp(e.target.value);
  };

  const startTimer = () => {
    let count = 30;
    setDisableResend(true);
    const timerId = setInterval(() => {
      count -= 1;
      setTimer(count);

      if (count === 0) {
        clearInterval(timerId);
        setDisableResend(false);
      }
    }, 1000);
  };

  const handleResendOtp = async (e: any) => {
    try {
      e.preventDefault();
      setSuccessMsg("otp resent successfully");
      setDisableResend(true);
      startTimer();

      const res = await axios.post(
        `${serverUrl}/resend-otp`,
        {
          email,
        },
        {
          headers: {
            "content-type": "application/json",
          },
        }
      );

      if (res.data.error === false) {
        setSuccessMsg("OTP resent successfully");
        setDisableResend(true);
        setTimer(30);
        startTimer();
      }
    } catch (error) {
      console.log(error);
    }
  };

  const handleLogin = async (e: any) => {
    e.preventDefault();

    const res = await axios.post(
      `${serverUrl}/verify-otp`,
      {
        email,
        otp,
      },
      {
        headers: {
          "content-type": "application/json",
        },
      }
    );

    console.log(res.data);
  };

  return (
    <div className="otp-container">
      {showOtpForm && (
        <div style={{ height: "100%" }}>
          <ToastContainer />
          <div className="otp-container">
            <h2>Enter OTP</h2>
            <p>We have sent a 6-digit OTP in your email.</p>
            <div>
              <input
                type="text"
                className="otp-input"
                maxLength={6}
                onChange={handleOtpChange}
              />
            </div>
            {(showOtpForm || otpExpired || incorrectAttempts >= 3) && (
              <div className="resend-otp-btn">
                <span
                  onClick={handleResendOtp}
                  onKeyDown={(e) => e.keyCode === 13}
                  tabIndex={0}
                  role="button"
                  style={{
                    opacity: buttonOpacity,
                    pointerEvents: disableResend ? "none" : "auto",
                  }}
                  //@ts-ignore
                  disabled={disableResend}
                >
                  Resend OTP
                  {disableResend ? `(${timer}s)` : ""}
                </span>
              </div>
            )}

            <button className="submit-btn" onClick={handleLogin} type="button">
              Submit
            </button>
            {loginError && <div style={{ color: "red" }}>{loginError}</div>}
            {successMsg && <div style={{ color: "green" }}>{successMsg}</div>}
            {errorMessage && <div style={{ color: "red" }}>{errorMessage}</div>}
          </div>
        </div>
      )}
    </div>
  );
}

解决方案

这个报错本质是React与react-toastify的类型定义不兼容,按以下步骤排查修复:

  1. 升级依赖版本
    确保react-toastify版本与React版本匹配:React 18需要react-toastify@9.x及以上,同时更新React类型定义:

    npm install react-toastify@latest @types/react@latest @types/react-dom@latest
    
  2. 移除冗余类型包
    react-toastify自v8起自带类型定义,无需单独安装@types/react-toastify,如果之前装过,直接卸载:

    npm uninstall @types/react-toastify
    
  3. 调整TypeScript配置
    在tsconfig.json中添加或修改以下配置,跳过库类型检查以规避冲突:

    {
      "compilerOptions": {
        "jsx": "react-jsx",
        "skipLibCheck": true
      }
    }
    
  4. 优化组件放置
    建议将ToastContainer放在组件树的顶层(比如App.tsx中),避免在条件渲染的嵌套层级中重复渲染,也能减少潜在的类型问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 04:54:54