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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 22:14:58