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
相关产品推荐
相关产品推荐

