为什么我会在 React/TypeScript/MUI 对话框中看到这种奇怪的行为?

问题描述 投票:0回答:1

我在 React/TypeScript/MUI 应用程序的流程中出现了一个对话框,其中显示了几个按钮。当您选择每个按钮时,对话框函数会将按钮的值添加到状态数组中并将其从对话框中删除。这是一种工作方式,我的意思是初始对话框看起来不错,但是在按下第一个按钮后,原始对话框仍保留在后台,并且一个新对话框出现在其顶部。随后按下每个按钮,顶部对话框都会正常工作。有人可以解释一下为什么原始对话框出现在后台吗? 【对话框上的对话框效果】

这是我的对话框代码:

import { useContext, useEffect, useMemo, useState } from "react";
import { AppContext } from "../AppContextProvider";
import { Button, Dialog, DialogTitle, Snackbar, styled } from "@mui/material";
import { grey } from "@mui/material/colors";
import { allGearEffectTypes, GearEffectConfig, GearEffectType, GearType } from "../CharStore/CharData";

const StyledButton = styled(Button)(({ theme }) => ({
  color: "black",
  backgroundColor: grey[400],
  borderRadius: 25,
  '&:hover': {
    backgroundColor: grey[500],
  }
}));

type AddGearEffectDlgProps = {
  openDlg: boolean,
  closeDlg: (effects?: Array<GearEffectType> | null) => void;
  addGear: (gear: GearType) => void;
}

const AddGearEffectDlg = (props: AddGearEffectDlgProps) => {
  const {openDlg, closeDlg, addGear} = props;
  const [char, setChar] = useContext(AppContext)!;
  const [gearEffects, setGearEffects] = useState<Array<GearEffectType> | null>(null);

  const effectButtons = useMemo(() => {
    const effectButtons: Array<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>> = [];
    allGearEffectTypes.forEach((effectType, index) => {
      if (!gearEffects || !(gearEffects.includes(effectType))) {
        effectButtons.push(
          <StyledButton key={index} onClick={() => handleAddEffect(effectType)} variant="contained" sx={{ marginTop: 1, marginLeft: 1, marginRight: 1 }}>{effectType}</StyledButton>
        );
      }
    });
    return effectButtons;
  }, [char, gearEffects]);

  const handleAddEffect = (effectType: GearEffectType) => {
    const newEffects: Array<GearEffectType> = gearEffects ? [...gearEffects] : [];
    newEffects.push(effectType);
    setGearEffects(newEffects);
  }

  const handleCloseAddGearEffectDlg =  () => {
    closeDlg(gearEffects);
  }
  
  return (
    <Dialog onClose={handleCloseAddGearEffectDlg} open={openDlg}>

      <DialogTitle style={{ marginTop: -10, marginBottom: -20 }}>Pick Effect to add</DialogTitle>
      <>
        {effectButtons}
      </>
      <StyledButton onClick={handleCloseAddGearEffectDlg} sx={{ marginTop: 1, marginBottom: 1, marginLeft: 1, marginRight: 1 }}>Close</StyledButton>
    </Dialog>
  );
}

export default AddGearEffectDlg;

正如 Ryan 指出的那样,看到父组件也很重要(谢谢,我最初发布时很累)。如果您滚动到接近底部,您将看到包含错误的对话框

<AddGearValueDlg openDlg={openAddGearValueDlg} closeDlg={handleCloseGearValueDlg} gearName={gearName ? gearName : ""} gearEffects={gearEffects ? gearEffects : []} />

所以这里是:

import { Box, Button,Divider, Paper, Snackbar, Stack, Tooltip, Typography, } from "@mui/material";
import MobileBox from "../MobileBox";
import NonMobileBox from "../NonMobilebox";
import { grey } from "@mui/material/colors";
import GearDisplay from "./GearDisplay";
import { useContext, useEffect, useState } from "react";
import { CharDataType, GearEffectType, GearType } from "../CharStore/CharData";
import { AppContext } from "../AppContextProvider";
import AddGearEffectDlg from "./AddGearEffectDlg";
import UpPanel from "../UpPanel";
import LockCharacterBtn from "../LockCharacterBtn";
import NewGearNameDlg from "./NewGearNameDlg";
import AddGearValueDlg from "./AddGearValueDlg";

const NAME_STEP = "name_step";
const EFFECT_STEP = "effect_step";
const VALUE_STEP = "value_step";
const UNKNOWN_STEP = "unknown_step";
type stepType = typeof NAME_STEP | typeof EFFECT_STEP | typeof VALUE_STEP | typeof UNKNOWN_STEP; 

const GearPanel = () => {
  const [char, setChar] = useContext(AppContext)!;
  const [openNewGearNameDlg, setOpenNewGearNameDlg] = useState<boolean>(false);
  const [openAddGearEffectDlg, setOpenAddGearEffectDlg] = useState<boolean>(false);
  const [openAddGearValueDlg, setOpenAddGearValueDlg] = useState<boolean>(false);
  const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
  const [snackbarMsg, setSnackbarMsg] = useState<String | null>(null);
  const [currentStep, setCurrentStep] = useState<stepType>(UNKNOWN_STEP);
  const [gearName, setGearName] = useState<string | null>(null);
  const [gearEffects, setGearEffects] = useState<Array<GearEffectType> | null>();

  useEffect(() => {
    switch (currentStep) {
      case NAME_STEP:
        setGearName(null);
        setOpenNewGearNameDlg(true);
        setOpenAddGearEffectDlg(false);
        setOpenAddGearValueDlg(false);
        break;
      case EFFECT_STEP:
        setOpenNewGearNameDlg(false);
        setOpenAddGearEffectDlg(true);
        setOpenAddGearValueDlg(false);
        break;
      case VALUE_STEP:
        setOpenNewGearNameDlg(false);
        setOpenAddGearEffectDlg(false);
        setOpenAddGearValueDlg(true);
        break;
      default:
        setGearName(null);
        setOpenNewGearNameDlg(false);
        setOpenAddGearEffectDlg(false);
        setOpenAddGearValueDlg(false);
        return;
    }
  }, [currentStep]);

  const getNonMobile = () => {
    return (
      <NonMobileBox>
        NonMobileBox
      </NonMobileBox>
    )
  }

  const getMobile = () => {
    const handleClickAdd = () => {
      setCurrentStep(NAME_STEP);
    }

    const handleCloseNewGearNameDlg = () => {
      setOpenNewGearNameDlg(false);
    }

    const handleAddNewGearName = (gearName: string) => {
      setGearName(gearName);
      setCurrentStep(EFFECT_STEP);
    }

    const handleCloseAddEffectDlg = (effects: Array<GearEffectType> | null = null) => {
      if (effects) {
        setGearEffects(effects);
        setCurrentStep(VALUE_STEP);
      } else {
        setOpenAddGearEffectDlg(false);
      }
    }

    const handleAddGear = (gear: GearType) => {
      let hasGear: boolean = false;
      char.gear.forEach(gear => {
        if (gear.name === gear.name) {
          hasGear = true;
        }
      });
      if (hasGear) {
        setSnackbarMsg(`You already have gear named ${gear.name}, please pick a new name.`);
        setOpenSnackbar(true);
      } else {
        let newChar = {...char};
        newChar.gear.push(gear);
        setChar(newChar);
      }
    }

    const handleCloseGearValueDlg = (values: Array<GearType> | null = null) => {
      if (values && values.length > 0) {
        setOpenAddGearValueDlg(false);
        // TODO: More!
      } else {
        setCurrentStep(UNKNOWN_STEP);
      }
    }

    const toggleEquipGear = (gearName: string) => {
      let newChar = { ...char };
      newChar.gear.forEach(gear => {
        if (gear.name === gearName) {
          gear.equipped = !gear.equipped;
          if (gear.equipped) {
            setSnackbarMsg("Gear equipped");
          } else {
            setSnackbarMsg("Gear unequipped");
          }
          setOpenSnackbar(true);
        }
      });
      setChar(newChar);
    }

    const handleRemoveGear = (gearName: string) => {
      let newChar = { ...char };
      let newGear: Array<GearType> = newChar.gear.filter(gear => gear.name !== gearName);
      newChar.gear = newGear;
      setChar(newChar);
    }

    const handleCloseSnackbar = () => {
      setSnackbarMsg(null);
      setOpenSnackbar(false);
    }
  
    const getGear = () => {
      const gearArray: Array<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>> = [];
      const groups: Array<string> = [];
      if (char.gear) {
        char.gear.forEach((gear, index) => {
          gearArray.push(
            <div key={index} style={{ marginLeft: 5 }}>
              <GearDisplay gear={gear} clickHandler={toggleEquipGear} />
            </div>)
        });
      }
      return <>{gearArray}</>;
    }

    const getAddGearBtn = () => {
      if (char.locked) return null;
      return (
        <Button
          onClick={handleClickAdd}
          variant="contained"
          style={{
            marginLeft: 5,
            marginRight: 5,
            marginTop: "10px",
            backgroundColor: grey[300],
            color: "black",
            borderRadius: "15px 15px 15px 15px"
          }}
        >
          <Typography variant="body2" fontWeight={900}>Add Gear</Typography>
        </Button>
      );
    }

    return (
      <MobileBox>

        <AddGearEffectDlg
          openDlg={openAddGearEffectDlg}
          closeDlg={() => {}}
          addGear={() => {}}
        />
        <Snackbar
          open={openSnackbar}
          autoHideDuration={2000}
          onClose={handleCloseSnackbar}
          message={snackbarMsg}
        />

        <Paper variant="outlined" style={{ marginTop: 85, marginLeft: -8, backgroundColor: grey[400], minWidth: "100vw", maxWidth: "100vw" }}>
          <UpPanel nav='/action_panel' text='Actions' />

          <Divider style={{ marginTop: 5, marginBottom: 5 }} />

          <Stack style={{ marginLeft: 5, marginRight: 5 }}>

            <Box bgcolor={grey[300]} style={{ marginLeft: 5, marginRight: 5, marginBottom: 10, paddingLeft: 10, paddingRight: 10, borderRadius: 5, display: "flex", justifyContent: "center", alignContent: "center" }}>
              <Typography variant="h4" fontWeight={900}>Gear</Typography>
            </Box>

            <NewGearNameDlg openDlg={openNewGearNameDlg} closeDlg={handleCloseNewGearNameDlg} addName={handleAddNewGearName} />
            <AddGearEffectDlg openDlg={openAddGearEffectDlg} closeDlg={handleCloseAddEffectDlg} addGear={handleAddGear} />
            <AddGearValueDlg openDlg={openAddGearValueDlg} closeDlg={handleCloseGearValueDlg} gearName={gearName ? gearName : ""} gearEffects={gearEffects ? gearEffects : []} />
            {getGear()}

            {getAddGearBtn()}

            <LockCharacterBtn />

            <Divider style={{ marginTop: 5, marginBottom: 5 }} />

          </Stack>

        </Paper>
      </MobileBox>
    );
  }

  return (
    <>
      {getMobile()}
      {getNonMobile()}
    </>
  );
}

export default GearPanel;

对于延迟,我深表歉意,瑞安,我工作时间很疯狂,而且很累。我确实有一个codesandbox,但它是整个应用程序。我需要一段时间才能将其配对(我真的需要一些睡眠),但如果你现在确实想看它,它就在这里: CodeSandbox 项目

为了重现该错误,您可以单击面板上的向下按钮,直到到达 Gear 面板(最后一个),此时您位于 src/Components/Gear/GearPanel.tsx 文件中。当您单击“添加齿轮”按钮时,将打开“新齿轮名称”对话框。添加名称(任意)并单击“选择”按钮后,您将位于 src/Components/Gear/AddGearEffectDlg.tsx 文件中。 GearPanel.tsx 是父项,AddGearEffectDlg.tsx 是子项。

AddGearEffect 中发生的情况是,您可以添加一个初始的齿轮效果列表,当您选择它们时,它们会从对话框中消失,并添加到一个状态变量中,该状态变量应该在 AddGearEffectDlg 关闭时传递回父级。初始的 AddGearEffectDlg 具有所有齿轮效果,但是当您选择第一个效果时,似乎在该效果之上打开了第二个 AddGearEffectDlg,其中包含正确的列表(缺少所选效果)。对于此后选择的每个效果,新对话框的列表会适当缩小,但是原始的 AddGearEffectDlg 位于后台。

父项 GearPanel.tsx 具有三个不同的对话框,应该按顺序显示这些对话框。 NewGearNameDlg.tsx、AddGearEffectDlg 和 AddGearValueDlg。它通过状态变量 currentStep 来跟踪这一点,该变量可以是 NAME_STEP、EFFECT_STEP、VALUE_STEP 或 UNKOWN_STEP。它使用这些状态来通过设置 openNewGearNameDlg、openAddGearEffectDlg 和 openAddGearValueDlg 的正确状态来指示应打开哪个对话框,并且在应该交换对话框时通过设置所有对话框来确保仅打开其中一个对话框。每个对话框都有一个传递给它们的关闭对话框方法,用于回调父级以设置它们收集的数据,并告诉父级在适当的情况下交换到下一个对话框。

抱歉,如果这有点漫无目的。我在过去 40 个小时里睡了大约 2 个小时,所以我将停止在这里打字。

reactjs typescript material-ui
1个回答
0
投票

我认为问题是负责关闭对话框的 closeDlg 函数在第一次按下按钮后没有被调用。这可以防止原始对话框被关闭并使其保持可见。 试试这个方法

const handleAddEffect = (effectType: GearEffectType) => {
  const newEffects: Array<GearEffectType> = gearEffects ? [...gearEffects] : [];
  newEffects.push(effectType);
  setGearEffects(newEffects);
  closeDlg(newEffects); // Close the dialog after adding the effect
}

如果有的话请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.