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

React+TypeScript+Material UI可复用Dialog组件无法显示求助

解决Material UI Dialog无法弹出及标签点击空白问题

核心问题分析与修复步骤

1. 阻止标签默认跳转行为

点击标签会触发浏览器默认跳转动作,导致页面空白。需在点击事件中调用e.preventDefault()阻止默认行为。

2. 修复ReusableDialog的Cancel按钮点击事件

Cancel按钮的回调仅引用了closeDialog函数,未执行,需添加括号调用函数。

3. 移除ReusableDialog内部冗余状态

组件内部维护的open状态完全依赖Props传递的show值,无需额外维护,直接使用Props控制Dialog显示即可。

4. 绑定childFormDialog的表单提交事件

表单未绑定onSubmit事件,导致点击提交按钮无法触发formik的提交逻辑,需添加onSubmit={formik.handleSubmit}。

5. 修正组件命名规范

React组件必须首字母大写,childFormDialog改为ChildFormDialog,避免被识别为原生HTML标签。

6. 处理secureLocalStorage的空值情况

从localStorage取值时需处理可能的null,避免调用toString()报错。

7. 修正路由嵌套问题

Parent组件路由不应嵌套在Dashboard下,调整为同级路由确保可正常访问。


修正后的完整代码

ReusableDialog.tsx

import React, { ReactNode } from 'react';
import { Button, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material';

type Props = {      
    show: boolean,
    title: string,
    body: ReactNode,      
    onSubmitButtonClicked: () => void;       
    closeDialog: () => void;   
};

export default function ReusableDialog(props: Props) {
    return (    
        <Dialog 
            open={props.show}
            onClose={props.closeDialog}
            aria-labelledby='dialog-title'
            aria-describedby='dialog-description'
        >
            <DialogTitle id='dialog-title'>{props.title}</DialogTitle>
            <DialogContent>
                {props.body}
            </DialogContent>
            <DialogActions>
                <Button onClick={props.closeDialog}>Cancel</Button>
                <Button autoFocus onClick={props.onSubmitButtonClicked}>Submit</Button>
            </DialogActions>
        </Dialog>
    );
}

ChildFormDialog.tsx(首字母大写修正)

import React, { useState, useLayoutEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { Grid, TextField, Button, InputAdornment } from '@mui/material';
import { CallToActionIcon, PersonOutlineIcon, EmailIcon, PhoneIphoneIcon, EventNoteIcon, SendIcon } from '@mui/icons-material';
import { useFormik } from 'formik';
import * as yup from 'yup';

interface PaymentTypeProps{
    paymenttype: string;
}

const baseURL = process.env.REACT_APP_API_BASE_URL_LOCAL;

const ChildFormDialog = (props: PaymentTypeProps) => {
    const navigate = useNavigate();    
    const [submitting, setSubmitting] = useState("");
    const [errormsg, setErrorMsg] = useState("");
    const [fullname, setFullName] = useState<string>('');
    const [email, setEmail] = useState<string>('');
    const [phone, setPhone] = useState<string>('');                 
    const [paymenttypes, setPaymenttypes] = useState(props.paymenttype)       

    useLayoutEffect(()=>{
        // 处理localStorage取值可能为null的情况
        const storedFullname = secureLocalStorage.getItem('fullname');
        const storedEmail = secureLocalStorage.getItem('email');
        const storedPhone = secureLocalStorage.getItem('phone');
        
        setFullName(storedFullname ? storedFullname.toString() : '');
        setEmail(storedEmail ? storedEmail.toString() : '');        
        setPhone(storedPhone ? storedPhone.toString() : '');  
    }, []);

    const validationSchema = yup.object({
        fullname: yup.string().required('Your full name is needed'),
        email: yup.string().required('An email address is needed'),
        phone: yup.string().required('Phone number is needed'),
        amount: yup.string().required('Please type an amount'),                 
        paymenttitle: yup.string().required('Please indicate title for this payment'),
        paymentdescription: yup.string(),                   
    });    
    
    const formik = useFormik({
        initialValues: {
            fullname: fullname, 
            email: email, 
            phone: phone, 
            amount: '',        
            paymenttitle: '',        
            paymentdescription: '',        
        },
        validationSchema: validationSchema, 
        onSubmit: async (values) => {            
            handleFormSubmit();   
        }  
    });

    const handleFormSubmit = () => {
        const data = {           
            fullname: formik.values.fullname,
            email: formik.values.email,
            phone: formik.values.phone,
            amount: formik.values.amount,    
            paymenttitle: formik.values.paymenttitle,
            paymentdescription: formik.values.paymentdescription,
        }        
        
        if(paymenttypes === 'A') {
            // configPaymentAPI_A(data.amount, data.email, data.phone, data.fullname, data.paymenttitle, data.paymentdescription)
        } else {
            // configPaymentAPI_B(data.email, data.amount)
        }
    };

    return (
        <form style={{width: '100%'}} onSubmit={formik.handleSubmit}>
            <div className="row px-3">  
                <Grid container direction="column" spacing={2}>
                    <Grid item>
                        <TextField 
                            style={{width: '100%'}} 
                            label="Payment Title" 
                            name="paymenttitle"
                            placeholder="Payment Title" 
                            variant="standard"
                            type="text" 
                            value={formik.values.paymenttitle}
                            onChange={formik.handleChange}
                            error={formik.touched.paymenttitle && Boolean(formik.errors.paymenttitle)}
                            helperText={formik.touched.paymenttitle && formik.errors.paymenttitle}
                            InputProps={{
                                startAdornment: (
                                    <InputAdornment position="start">
                                        <CallToActionIcon color="primary" />
                                    </InputAdornment>
                                ),
                            }} 
                        />
                    </Grid>
                    <Grid item>
                        <TextField 
                            style={{width: '100%'}} 
                            label="Full Name" 
                            name="fullname"
                            placeholder="Your Full Name" 
                            variant="standard"
                            type="text" 
                            aria-readonly
                            value={formik.values.fullname}
                            onChange={formik.handleChange}
                            error={formik.touched.fullname && Boolean(formik.errors.fullname)}
                            helperText={formik.touched.fullname && formik.errors.fullname}
                            InputProps={{
                                startAdornment: (
                                    <InputAdornment position="start">
                                        <PersonOutlineIcon color="primary" />
                                    </InputAdornment>
                                ),
                            }} 
                        />
                    </Grid>
                    <Grid item>
                        <TextField 
                            style={{width: '100%'}} 
                            label="Email Address" 
                            name="email"
                            placeholder="Your Email" 
                            variant="standard"
                            type="email" 
                            aria-readonly
                            value={formik.values.email}
                            onChange={formik.handleChange}
                            error={formik.touched.email && Boolean(formik.errors.email)}
                            helperText={formik.touched.email && formik.errors.email}
                            InputProps={{
                                startAdornment: (
                                    <InputAdornment position="start">
                                        <EmailIcon color="primary" />
                                    </InputAdornment>
                                ),
                            }} 
                        />
                    </Grid>
                    <Grid item>
                        <TextField 
                            style={{width: '100%'}} 
                            label="Phone Number" 
                            name="phone"
                            placeholder="Your Phone Number" 
                            variant="standard"
                            type="number" 
                            aria-readonly
                            value={formik.values.phone}
                            onChange={formik.handleChange}
                            error={formik.touched.phone && Boolean(formik.errors.phone)}
                            helperText={formik.touched.phone && formik.errors.phone}
                            InputProps={{
                                startAdornment: (
                                    <InputAdornment position="start">
                                        <PhoneIphoneIcon color="primary" />
                                    </InputAdornment>
                                ),
                            }} 
                        />
                    </Grid>
                    <Grid item>
                        <TextField 
                            style={{width: '100%'}} 
                            label="Amount" 
                            name="amount"
                            placeholder="Amount To Be Paid" 
                            variant="standard"
                            type="number" 
                            value={formik.values.amount}
                            onChange={formik.handleChange}
                            error={formik.touched.amount && Boolean(formik.errors.amount)}
                            helperText={formik.touched.amount && formik.errors.amount}
                            InputProps={{
                                startAdornment: (
                                    <InputAdornment position="start">
                                        <CallToActionIcon color="primary" />
                                    </InputAdornment>
                                ),
                            }} 
                        />
                    </Grid>
                    <Grid item>
                        <TextField 
                            style={{width: '100%'}} 
                            label="Payment Description" 
                            name="paymentdescription"
                            placeholder="Type some notes for this payment" 
                            variant="standard"
                            type="text" 
                            value={formik.values.paymentdescription}
                            onChange={formik.handleChange}
                            error={formik.touched.paymentdescription && Boolean(formik.errors.paymentdescription)}
                            helperText={formik.touched.paymentdescription && formik.errors.paymentdescription}
                            InputProps={{
                                startAdornment: (
                                    <InputAdornment position="start">
                                        <EventNoteIcon color="primary" />
                                    </InputAdornment>
                                ),
                            }} 
                        />
                    </Grid>
                    <Grid item>
                        <Button 
                            type="submit"
                            variant="contained"  
                            startIcon={<SendIcon />} 
                            disableRipple 
                        >Remit Your Payment</Button>
                    </Grid>
                </Grid>   
            </div>
        </form>
    );
}

export default ChildFormDialog;

Parent.tsx

import React, { useState } from 'react';
import ReusableDialog from './ReusableDialog';
import ChildFormDialog from './ChildFormDialog';

const Parent = () => {
    const [paymentTypState, setPaymentTypState] = useState<string>('');
    const [open, setOpen] = useState(false); 

    const onclickPaymentA = (e: React.MouseEvent<HTMLAnchorElement>) => {
        e.preventDefault(); // 阻止默认跳转
        setOpen(true); 
        setPaymentTypState('A');  
    };

    const onclickPaymentB = (e: React.MouseEvent<HTMLAnchorElement>) => {
        e.preventDefault(); // 阻止默认跳转
        setOpen(true); 
        setPaymentTypState('B');  
    };

    const handleDialogOnSubmit = () => {
        // 处理提交逻辑
        setOpen(false); // 提交后关闭弹窗
    };

    return (
        <>
            <a onClick={onclickPaymentA}>
                <img src="../assets/images/paymentA.png" style={{width:300, height: 150}}/>
            </a>

            <a onClick={onclickPaymentB}>
                <img src="../assets/images/paymentB.png" style={{width:300, height: 150}}/>
            </a>

            <ReusableDialog
                show={open}
                title='Top Up Your Wallet'
                body={<ChildFormDialog paymenttype={paymentTypState} />}
                closeDialog={() => setOpen(false)}        
                onSubmitButtonClicked={handleDialogOnSubmit}
            />
        </>
    );
};

export default Parent;

路由配置修正

import React from "react";
import { BrowserRouter, Routes, Route } from 'react-router-dom';

import Login from './Login';
import Register from './Register';
import Dashboard from './Dashboard';
import Parent from './Parent';
import AuthenticatedRoute from './AuthenticatedRoute';

const AppRouter = () => {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/login" element={<Login />} />
                <Route path="/register" element={<Register />} />
                <Route path="/dashboard" element={<AuthenticatedRoute element={<Dashboard />} />} />
                {/* 调整为同级路由 */}
                <Route path="/parent" element={<AuthenticatedRoute element={<Parent />} />} />
            </Routes>
        </BrowserRouter>
    );
};

export default AppRouter;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 05:24:20