React游戏得分表单比分相同时弹窗不显示,如何解决?
问题描述
我正在开发一款记录游戏得分的React表单应用,需求是当比分相同时弹出对话框让用户选择获胜玩家。测试按钮能正常触发弹窗,但整合到带多状态的表单后,弹窗无法显示。我试过把useState换成普通变量,问题仍未解决。相关代码如下:
Form组件
import {useState, SyntheticEvent} from 'react'; import { useParams } from 'react-router-dom'; import axios from 'axios'; import { FormNumberElement } from './form elements/FormNumberElement'; import { DialogBox } from '../DialogBox'; export const GameForm = () => { const {eventSlug} = useParams() const [winnerIsA, setWinnerIsA] = useState<null | boolean>(null) const [scoreA, setScoreA] = useState<number | null>(null) const [scoreB, setScoreB] = useState<number | null>(null) // const [showDialogBox, setShowDialogBox] = useState(false) let showDialogBox = false function setShowDialogBox(value: boolean){ showDialogBox = value } // process form before submitting const handleClick = (e: SyntheticEvent) => { // e.preventDefault() if (scoreA == null || scoreB == null){ alert("score is required") return } // assign winner if (scoreA > scoreB){ setWinnerIsA(true) } if (scoreA < scoreB){ setWinnerIsA(false) } // if score is tied, show dialog box to choose a winner if (scoreA == scoreB && winnerIsA === null){ setShowDialogBox(true) console.log('showDialogBox: ', showDialogBox) return } console.log('sending data to backend...') async function sendData(){ const {data} = await axios.post('api/events/create/', { 'event_slug': eventSlug, 'score_a': scoreA, 'score_b': scoreB, 'winner_is_a': winnerIsA }, {withCredentials: true})} sendData() } return( <> {showDialogBox ? DialogBox( 'Tied Score', 'Please select a winner.', 'Player A', 'Player B', setWinnerIsA(true), setWinnerIsA(false), setShowDialogBox(false)) : ''} <form> <h1 className='h1'>New Game</h1> <FormNumberElement setValue={setScoreA} elementName='score-a' labelText='Score A'/> <FormNumberElement setValue={setScoreB} elementName='score-b' labelText='Score B'/> <button className='submit-button' type='button' onClick={(e) => handleClick(e)}>Add Game</button> <button type='button' onClick={()=>setWinnerIsA(null)}>reset winner_is_a</button> </form> </> )}
DialogBox组件
export function DialogBox( title:string, msg:string, textActionA: string, textActionB: string, actionA: void | (() => void), actionB: void | (() => void), cancelButton?: void | (() => void) ){ return( <div className='dialog-ovelay'> <div className='dialog'> <header> <h3>{title}</h3> </header> <div className='dialog-msg'> <p>{msg}</p> </div> <footer> <div className='controls'> <button className='button button-danger doAction' onClick={() => actionA?.()}>{textActionA}</button> <button className='button button-default cancelAction' onClick={() => actionB?.()}>{textActionB}</button> {cancelButton ? <button className='button button-default' onClick={cancelButton}>Cancel</button> : ''} </div> </footer> </div> </div> )}
FormNumberElement组件
import { useState, Dispatch, SetStateAction } from 'react' export const FormNumberElement = ({ setValue, labelText, elementName, }:{ setValue: Dispatch<SetStateAction<number | null>> labelText: string, elementName: string, }) => { // keep label from covering the input when filled in const [className, setClassName] = useState<string>('') function handleChange(entry:number) { setValue(entry) if (entry != 0) { setClassName('jumped') }else{ setClassName('') } } return ( <div className='form-input'> <input id='nameInput' name={elementName} type='number' onChange={e => handleChange(e.target.valueAsNumber)} /> <label className={className} htmlFor='nameInput'>{labelText}</label> </div> ) }
解决方案
弹窗无法显示的核心问题有三个,逐一修复即可:
1. 用useState管理弹窗显示状态
React不会追踪普通变量的变化,你用let showDialogBox = false代替useState,修改变量值后不会触发组件重渲染,自然看不到弹窗。恢复useState:
const [showDialogBox, setShowDialogBox] = useState(false)
2. 正确调用DialogBox组件
直接调用DialogBox()函数不是React组件的正确使用方式,应该用JSX语法<DialogBox />,同时传递回调函数时不能直接执行(比如setWinnerIsA(true)会立即执行,而不是点击按钮时执行),要包裹成匿名函数:
{showDialogBox && ( <DialogBox title="Tied Score" msg="Please select a winner." textActionA="Player A" textActionB="Player B" actionA={() => { setWinnerIsA(true); setShowDialogBox(false); sendData(); // 选完赢家后提交数据 }} actionB={() => { setWinnerIsA(false); setShowDialogBox(false); sendData(); }} cancelButton={() => setShowDialogBox(false)} /> )}
3. 修正DialogBox的props类型和事件绑定
DialogBox的actionA/actionB应该定义为函数类型,且onClick直接绑定函数即可,不需要额外包裹:
export function DialogBox({ title, msg, textActionA, textActionB, actionA, actionB, cancelButton }: { title: string; msg: string; textActionA: string; textActionB: string; actionA: () => void; actionB: () => void; cancelButton?: () => void; }) { return ( <div className='dialog-ovelay'> <div className='dialog'> <header> <h3>{title}</h3> </header> <div className='dialog-msg'> <p>{msg}</p> </div> <footer> <div className='controls'> <button className='button button-danger doAction' onClick={actionA}>{textActionA}</button> <button className='button button-default cancelAction' onClick={actionB}>{textActionB}</button> {cancelButton && ( <button className='button button-default' onClick={cancelButton}>Cancel</button> )} </div> </footer> </div> </div> ); }
4. 优化Form逻辑和组件细节
- 把sendData提取为独立函数,方便复用:
const sendData = async () => { if (winnerIsA === null || scoreA === null || scoreB === null) return; try { await axios.post('api/events/create/', { event_slug: eventSlug, score_a: scoreA, score_b: scoreB, winner_is_a: winnerIsA }, { withCredentials: true }); // 提交成功后重置表单 setScoreA(null); setScoreB(null); setWinnerIsA(null); } catch (err) { console.error('提交失败:', err); } };
- 简化handleClick逻辑:
const handleClick = (e: SyntheticEvent) => { e.preventDefault(); if (scoreA == null || scoreB == null) { alert("得分不能为空"); return; } if (scoreA !== scoreB) { setWinnerIsA(scoreA > scoreB); sendData(); } else { setShowDialogBox(true); } };
- 修复FormNumberElement的id重复问题:
<input id={elementName} name={elementName} type='number' onChange={e => handleChange(e.target.valueAsNumber)} /> <label className={className} htmlFor={elementName}>{labelText}</label>
内容的提问来源于stack exchange,提问作者justin107d
相关产品推荐
相关产品推荐

