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

React中按钮点击后无法重复打开对话框的问题求助

React对话框无法重复打开的问题修复

问题描述

按钮打开对话框后,无法再次点击按钮打开该对话框,必须返回页面或刷新才能重新打开。经调试后仅能打开两次,无法支持用户按需多次打开修改地址。

核心逻辑错误分析

  • 冗余状态导致控制混乱:父组件HomeLocation和子组件HomeLocationDialog都维护了shouldResetDialog状态,且父组件用该状态控制子组件的显示属性,成功回调后将其设为false,导致后续打开时子组件无法触发显示。
  • 对话框显示依赖错误:父组件渲染对话框的条件是自身showDialog,但传递给子组件的showDialog属性却是shouldResetDialog,双重状态控制导致逻辑冲突。
  • 子组件状态未重置:每次打开对话框时,子组件的updatedLocation、cities等状态残留之前的交互数据,影响新一次的对话框使用。

解决方案

1. 简化状态管理

移除冗余的shouldResetDialog状态,统一由父组件的showDialog控制对话框的显示与隐藏。

2. 修复显示属性传递

父组件传递给子组件的showDialog直接使用自身的同名状态,确保显示逻辑一致。

3. 子组件状态重置

添加useEffect监听对话框打开事件,重置子组件内部状态,保证每次打开都是初始干净的状态。

修正后的代码

HomeLocation/index.js

function HomeLocation({ onHomeLocationUpdated }) {

  const [selectedLocation, updateLocation] = useState();
  const [showDialog, setShowDialog] = useState(false);
  const [progressBar, setProgressBar] = useState(true);
  const [errorComponent, showErrorComponent] = useState(false);

  useEffect(() => {
    fetchHomeLocation();
  }, []);

  useEffect(() => {
    onHomeLocationUpdated(selectedLocation);
  }, [selectedLocation]);

  const fetchHomeLocation = async () => {
    setProgressBar(true);
    showErrorComponent(false);
    ShippingData.getShippingHomeLocation().then((data) => {
      updateLocation(data);
    }).catch(() => {
      showErrorComponent(true);
    }).then(() => {
      setProgressBar(false)
    });
  }

  const openDialog = () => {
    setShowDialog(true);
  }

  const closeDialog = () => {
    setShowDialog(false);
  }

  return <div className='shippingItemContainer shippingHomeCityContainer' onClick={openDialog}>
    <CustomLabel value={'Home city'} />
    {
      progressBar ? <CircularProgress size={24} color="primary" style={{ marginTop: '8px' }} /> :
        errorComponent ? <CustomErrorComponent onRetryClick={fetchHomeLocation} /> : <div className='updateCityContainer'>
          {(selectedLocation || {}).name && <span className='city'>{selectedLocation.name}</span>}
          <ChangeText text={isNil((selectedLocation || {}).name) ? 'Select' : 'Change'} onClick={openDialog} />
        </div>
    }
    {showDialog && <HomeLocationDialog
      showDialog={showDialog}
      onClose={closeDialog}
      onSuccess={(location) => {
        updateLocation(location);
        setShowDialog(false);
      }}
    />}
  </div>
}

export default withRouter(HomeLocation);

HomeLocationDialog/index.js

export default function ({ showDialog, onClose, onSuccess }) {

    const noResultTextWhenCharLessThanThree = 'Type full city name';
    const noResultText = 'No results found.';

    const [cities, setCities] = useState([]);
    const [updatedLocation, setUpdatedLocation] = useState(null);

    const [progressBar, setProgressBar] = useState(false);
    const [loadingData, setLoadingData] = useState(false);

    const [closeDialog, setCloseDialog] = useState(false);
    const [errorMessage, setErrorMessage] = useState(null);
    const [noOptionText, setNoOptionText] = useState(noResultTextWhenCharLessThanThree);

    // 对话框打开时重置内部状态
    useEffect(() => {
        if (showDialog) {
            setUpdatedLocation(null);
            setCities([]);
            setErrorMessage(null);
            setNoOptionText(noResultTextWhenCharLessThanThree);
            setCloseDialog(false);
        }
    }, [showDialog]);

    const handleHomeLocationChange = (event, values) => {
        setUpdatedLocation(values);
    };

    const fetchCities = async (e) => {
        const content = (e.target.value).trim();
        if (!isNil(content) && content.length > 2) {
            setErrorMessage(null);
            setLoadingData(true);
            ShippingData.getAllCities(e.target.value).then((allCities) => {
                const sortedCities = sortBy(allCities, o => o.name);
                if (sortedCities.length === 0) {
                    setNoOptionText(noResultText);
                }
                setCities(sortedCities);
            }).catch((err) => {
                onApiFailure(err)
            }).then(() => {
                setLoadingData(false)
            });
        } else {
            setNoOptionText(noResultTextWhenCharLessThanThree);
            setCities([]);
        }
    }

    const updateHomeCity = async () => {
        setProgressBar(true);
        setErrorMessage(null);
        ShippingData.updateShippingHomeLocation({ cityId: updatedLocation.cityId }).then(() => {
            CacheRequest.deleteCacheForKeys([CacheKeys.shippingZonesCacheKey]);
            onApiSuccess();
        }).catch((err) => {
            onApiFailure(err)
        }).then(() => {
            setProgressBar(false)
        });
    }

    const onApiSuccess = () => {
        onSuccess(updatedLocation);
        onClose();
    }

    const onApiFailure = (error) => {
        setErrorMessage(error);
    }

    return showDialog && <DialogBox
        type={"simple"}
        dialogId={'#select-city'}
        show={showDialog}
        width={300}
        primaryBtnText={'SAVE'}
        onSuccess={updateHomeCity}
        onClose={onClose}
        disabledPrimaryButton={isNil(updatedLocation)}
        showProgressBar={progressBar}
        closeDialog={closeDialog}
    >
        <Fragment>
            <div className='cityDialogContent'>
                <p className='dialogTitle'>Select city</p>
                <Autocomplete
                    loading={loadingData}
                    noOptionsText={noOptionText}
                    onChange={handleHomeLocationChange}
                    id="autocomplete-city-select"
                    options={cities}
                    getOptionLabel={(option) => option.name + ', ' + option.state}
                    renderInput={(params) => <TextField 
                        onChange={fetchCities} 
                        autoFocus {...params} 
                        placeholder={'Type atleast 3 characters'} 
                        margin="normal"
                        InputProps={{
                            ...params.InputProps,
                            endAdornment: (
                              <React.Fragment>
                                {loadingData ? <CircularProgress color="inherit" size={18} /> : null}
                                {params.InputProps.endAdornment}
                              </React.Fragment>
                            ),
                          }} />}
                />
                {errorMessage && <CustomErrorComponent errorMessage={errorMessage} />}
            </div>
        </Fragment>
    </DialogBox>
}

关键修改说明

  • 父组件移除shouldResetDialog状态,直接用showDialog控制对话框,逻辑更清晰。
  • 子组件新增useEffect监听showDialog变化,每次打开时重置内部状态,避免残留数据干扰。
  • 子组件成功回调中直接调用父组件的onClose,统一由父组件管理对话框关闭逻辑,避免状态不同步。

内容的提问来源于stack exchange,提问作者Priyam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:35:12