React函数式子组件Props变更时状态未更新问题排查
问题与修复方案
问题描述
我有一个集成弹窗子组件的表单模板,弹窗用于展示登录、找回密码等AJAX请求的服务器响应,且会在一段时间后自动消失。首次触发请求时弹窗正常显示响应信息并自动消失,但在同一页面再次触发请求(AJAX无刷新)时,消息已更新但弹窗不再显示。排查后怀疑子组件的isShown状态未随消息更新同步变化,但无法解决,可能存在其他遗漏问题。
相关代码
AppForm.jsx(父模板)
function MainContent(props) { const { title, underTitle, children, buttonText, customHandler, bottomPart } = props; const [banner, setBanner] = useState({code: null, msg: null}) const reqHandler = async () => { customHandler().then((resp) => { setBanner({code: 'success', msg: resp.data.msg}) }).catch((err) => { setBanner({ code: 'error', msg: err.response.data.msg }) }); } return ( <ThemeProvider theme={formTheme}> <Container maxWidth="sm"> <AppFormPopup statusCode={banner.code} msg={banner.msg} /> <Box sx={{ mt: 7, mb: 12 }}> <Paper> {children} {/* 渲染派生表单页面所需的字段 */} <Button type='submit' sx={{ mt: 3, mb: 2 }} onClick={reqHandler}> {buttonText} </Button> </Paper> </Box> </Container> </ThemeProvider> ) } function AppForm(props) { return ( <> <AppFormNav /> <MainContent {...props} /> </> ); } AppForm.propTypes = { children: PropTypes.node, }; export default AppForm;
AppFormPopup.jsx(弹窗子组件)
function MainContent(props){ const { msg } = props; const [progress, setProgress] = useState(0); const [isShown, setIsShown] = useState(true); // 控制弹窗可见性的状态 useEffect(() => { const timer = setInterval(() => { setProgress((oldProgress) => { if(oldProgress === 100){ setIsShown(false); // return 0; } return Math.min(oldProgress + 10, 100); }); }, 500); return () => { clearInterval(timer); }; }); return ( <Grid container sx={{ display: isShown ? 'block' : 'none', }}> <Grid item sx={{ color: "white", pl: 1 }}> {msg} </Grid> </Grid> ) } export default function AppFormPopup({msg}) { if(msg === null) return; return ( <MainContent msg={msg} /> ) }
ForgotPass.jsx(继承表单模板的页面)
export default function ForgotPass() { const loc = useLocation().pathname; const mailRef = useRef(null); const reqHandler = async () => { const email = mailRef.current.value; if(email){ const resp = await axios.post(loc, {email}) return resp; } } return ( <AppForm buttonText="Send Reset Link" customHandler={reqHandler} > <Box sx={{ mt: 6 }}> <TextField autoFocus label="Email" inputRef={mailRef} /> </Box> </AppForm> ) }
问题根源
useEffect无依赖项:子组件MainContent中的useEffect没有设置依赖数组,会在每次组件渲染时重复执行,但首次执行后progress会走到100并将isShown设为false,后续消息更新时,useEffect触发后会直接检测到progress为100,再次将isShown设为false,导致弹窗无法显示。- 状态未重置:当新的
msg传入时,子组件没有重置isShown和progress的初始状态,弹窗仍保持之前的隐藏状态。
修复后的代码(核心修改在AppFormPopup.jsx)
function MainContent(props){ const { msg } = props; const [progress, setProgress] = useState(0); const [isShown, setIsShown] = useState(true); // 当msg变化时,重置弹窗状态并启动定时器 useEffect(() => { // 重置状态 setIsShown(true); setProgress(0); const timer = setInterval(() => { setProgress((oldProgress) => { const newProgress = Math.min(oldProgress + 10, 100); if(newProgress === 100){ setIsShown(false); } return newProgress; }); }, 500); return () => { clearInterval(timer); }; }, [msg]); // 添加msg作为依赖项,仅在msg变化时执行 return ( <Grid container sx={{ display: isShown ? 'block' : 'none', }}> <Grid item sx={{ color: "white", pl: 1 }}> {msg} </Grid> </Grid> ) } export default function AppFormPopup({msg}) { if(msg === null) return null; return ( <MainContent msg={msg} /> ) }
额外优化建议
- 可以给弹窗添加状态对应的样式(比如成功用绿色背景、错误用红色背景),利用传入的
statusCode实现。 - 可以将自动消失的时间做成可配置的props,提升组件复用性。
内容的提问来源于stack exchange,提问作者Fallen
相关产品推荐
相关产品推荐

