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
相关产品推荐
相关产品推荐

