如何在MUI Dialog组件中加载react-simple-captcha的Canvas元素?
问题:MUI Dialog嵌套表单中集成react-simple-captcha失败
现象
- 在未嵌套Dialog的页面中,react-simple-captcha可正常工作
- 嵌套到MUI Dialog的表单后,页面空白,控制台报错:
Uncaught TypeError: Cannot read properties of null (reading 'getContext') at loadCaptchaEnginge
- 注释掉调用
loadCaptchaEnginge的useEffect后,页面正常加载,点击刷新按钮时验证码能正常渲染
相关代码
Dialog组件代码
import { useEffect, useState } from "react"; import { loadCaptchaEnginge, LoadCanvasTemplateNoReload, validateCaptcha } from "react-simple-captcha"; import Dialog from "@mui/material/Dialog"; import IconButton from "@mui/material/IconButton"; import RefreshIcon from "@mui/icons-material/Refresh"; // some other imports const FormDialog = ({ open, setOpen }) => { //some states & variables declarations const handleSubmit = () => { //form submit event function } useEffect(() => { loadCaptchaEnginge(6, "#13baba", "#f5f5f5"); }, []); return ( <Dialog open={open} onClose={() => setOpen(false)}> <form onSubmit={handleSubmit}> {/* other elements */} <LoadCanvasTemplateNoReload /> <IconButton aria-label="Reload Captcha" size="small" color="primary" onClick={() => loadCaptchaEnginge(6, "#13baba", "whitesmoke");}} > <RefreshIcon /> </IconButton> </form> </Dialog> ) }
父组件代码
import { useState } from "react"; import FormDialog from "./FormDialog"; import Button from "@mui/material/Button" // some other imports const PageLayout = () => { const [dialogOpen, setDialogOpen] = useState(false); return ( <> {/* some other elements */} <Button variant="contained" onClick={() => setDialogOpen(true)} > Apply Now </Button> <FormDialog open={dialogOpen} setOpen={setDialogOpen} /> </> ) }
尝试过的方法
给Dialog添加useRef关联,修改useEffect:
const captchaRef = useRef(null); useEffect(() => { const captchaEl = captchaRef.current; if (captchaEl) { loadCaptchaEnginge(6, "#13baba", "#f5f5f5"); } }, []); return ( <Dialog open={open} onClose={() => setOpen(false)} ref={captchaRef}> {/* rest of the code */} </Dialog> )
修改后页面不再空白,但打开Dialog时验证码未初始渲染,仅点击刷新按钮后才显示。
解决方案
问题根源是loadCaptchaEnginge会直接操作DOM寻找canvas元素,但Dialog初始为隐藏状态(open默认false),此时LoadCanvasTemplateNoReload生成的canvas还未挂载到DOM中,导致找不到元素报错。
优化后的Dialog组件代码
import { useEffect, useState } from "react"; import { loadCaptchaEnginge, LoadCanvasTemplateNoReload, validateCaptcha } from "react-simple-captcha"; import Dialog from "@mui/material/Dialog"; import IconButton from "@mui/material/IconButton"; import RefreshIcon from "@mui/icons-material/Refresh"; // some other imports const FormDialog = ({ open, setOpen }) => { //some states & variables declarations const handleSubmit = () => { //form submit event function } // 监听open状态,Dialog打开时初始化验证码 useEffect(() => { if (open) { // 等待Dialog完全渲染后执行 const timer = setTimeout(() => { loadCaptchaEnginge(6, "#13baba", "#f5f5f5"); }, 100); return () => clearTimeout(timer); } }, [open]); return ( <Dialog open={open} onClose={() => setOpen(false)} // 推荐使用onEntered回调,更精准触发初始化 onEntered={() => loadCaptchaEnginge(6, "#13baba", "#f5f5f5")} > <form onSubmit={handleSubmit}> {/* other elements */} <LoadCanvasTemplateNoReload /> <IconButton aria-label="Reload Captcha" size="small" color="primary" onClick={() => loadCaptchaEnginge(6, "#13baba", "whitesmoke")} > <RefreshIcon /> </IconButton> </form> </Dialog> ) }
说明
- 监听
open状态,仅当Dialog打开时才触发验证码初始化 - 推荐使用MUI Dialog的
onEntered回调,该回调会在Dialog完全进入(动画完成、DOM元素已挂载)后执行,比setTimeout更可靠 - 移除空依赖的useEffect,改为依赖
open状态,确保每次Dialog打开都重新初始化验证码
内容的提问来源于stack exchange,提问作者gerypratama
相关产品推荐
相关产品推荐

