React中按钮点击触发状态变更导致表单无法提交的原因
问题
点击Save按钮时,希望在执行setUpdatingName(false)状态变更的同时触发所在<form>的提交,但添加状态变更后表单不再提交,移除该逻辑则表单能正常提交,这是为什么?
代码示例
function Item({name, _key}) { console.log('rendering Item') const [updatingName, setUpdatingName] = useState(false); const nameInputElement = useRef(); useEffect(() => { if (updatingName) { nameInputElement.current.focus(); } }, [updatingName]); function onUpdateClick() { setUpdatingName(true); } function onCancelClick() { setUpdatingName(false); } return ( <div> <input ref={nameInputElement} type="text" defaultValue={name} name="name" disabled={!updatingName} /> {!updatingName ? <> <button key={1} type="button" onClick={onUpdateClick}>Update</button> <button key={2} type="submit" name="delete" value={_key}>Remove</button> </> : <> <button key={3} type="submit" name="update" onClick={(e) => {setUpdatingName(false)}}>Save</button> <button key={4} type="button" onClick={onCancelClick}>Cancel</button> </>} </div> ) } function ItemList({title}) { return <> <h1>{title}</h1> <form method="post" onSubmit={(e) => {console.log('submitting');e.preventDefault()}}> <Item name={'small'} _key={0} /> </form> </> } export default ItemList;
原因解析
点击type="submit"按钮时,浏览器的事件执行顺序是:先触发按钮的onClick事件,再触发表单的onSubmit事件。
但在onClick中调用setUpdatingName(false)会立即触发React组件重渲染:Item组件的updatingName状态改变,原本显示的Save、Cancel按钮会被替换成Update、Remove按钮,这会导致当前的按钮和相关DOM节点被卸载。表单的onSubmit事件依赖原DOM节点存在才能完成触发流程,DOM被卸载后,后续的onSubmit事件就被中断了,所以控制台不会输出提交日志。
解决方案
方案1:将状态更新移到表单onSubmit回调中
把Save按钮的onClick逻辑移除,转而在表单的提交事件里处理状态变更(通过状态提升实现):
- 修改Item组件的Save按钮:
<button key={3} type="submit" name="update">Save</button>
- 状态提升,将
updatingName和setUpdatingName移到ItemList组件,通过props传递给Item:
function ItemList({title}) { const [updatingName, setUpdatingName] = useState(false); return <> <h1>{title}</h1> <form method="post" onSubmit={(e) => { console.log('submitting'); e.preventDefault(); // 根据提交按钮的name判断是否是保存操作 if (e.nativeEvent.submitter.name === 'update') { setUpdatingName(false); } }}> <Item name={'small'} _key={0} updatingName={updatingName} setUpdatingName={setUpdatingName} /> </form> </> }
这样表单提交流程不会被中断,提交完成后再更新状态,更符合React数据流规范。
方案2:延迟状态更新
利用setTimeout将状态更新放到宏任务队列,等表单提交事件处理完成后再执行重渲染:
<button key={3} type="submit" name="update" onClick={(e) => { setTimeout(() => setUpdatingName(false), 0); }}>Save</button>
这种方式简单直接,适合快速修复场景。
内容的提问来源于stack exchange,提问作者coderboy
相关产品推荐
相关产品推荐

