React中onBlur设置useState后控件需双击的问题求助
问题分析
你的问题核心是闭包陷阱导致的状态更新延迟:
TopBanner组件定义在App内部,每次App因状态更新重新渲染时,TopBanner会被重新创建,addLine函数会捕获旧的gridLine值。第一次点击按钮时,触发的是状态更新前的addLine,因此弹出旧值;状态更新后App重新渲染生成新的addLine,第二次点击才会获取到最新状态。- 输入框使用
defaultValue属于非受控组件用法,虽然onBlur会更新状态,但输入框本身不会同步状态变化,不符合React受控组件的规范。
修复方案
方案一:将子组件移出父组件,通过props传递状态
把TopBanner移到App外部,避免每次父组件渲染时重新创建子组件,同时通过props传递状态和更新方法,确保子组件能获取到最新状态:
const { useState } = React; // 独立定义子组件 const TopBanner = ({ gridLine, setGridLine }) => { const setHours = (event) => { setGridLine(prev => ({ ...prev, hours: event.target.value })); } const addLine = e => { e.preventDefault(); alert(`I am clicked, value = ${gridLine.hours}`); } return ( <div> <div className="form-control"> <label>Time Spend</label> {/* 改为受控组件,用value绑定状态 */} <input type="text" name="ticketTime" value={gridLine.hours} onBlur={setHours} data-required="1" /> </div> <div className="submit"> <a id="btnAddHours" href="#" className="btn" onClick={addLine}>Add</a> </div> </div> ); } function App() { const [gridLine, setGridLine] = useState({ hours: "", }); return ( <TopBanner gridLine={gridLine} setGridLine={setGridLine} /> ) } // 修正DOM挂载的选择器错误 const domContainer = document.querySelector('#mount'); const root = ReactDOM.createRoot(domContainer); root.render(<App/>);
方案二:使用useCallback锁定最新状态
如果要保留子组件在父组件内部的写法,用useCallback包裹addLine函数,明确依赖gridLine,确保每次状态更新时生成新的函数引用,获取最新状态:
const { useState, useCallback } = React; function App() { const [gridLine, setGridLine] = useState({ hours: "", }); const TopBanner = () => { const setHours = (event) => { setGridLine(prev => ({ ...prev, hours: event.target.value })); } // 用useCallback包裹,依赖gridLine确保获取最新值 const addLine = useCallback((e) => { e.preventDefault(); alert(`I am clicked, value = ${gridLine.hours}`); }, [gridLine]); return ( <div> <div className="form-control"> <label>Time Spend</label> <input type="text" name="ticketTime" value={gridLine.hours} onBlur={setHours} data-required="1" /> </div> <div className="submit"> <a id="btnAddHours" href="#" className="btn" onClick={addLine}>Add</a> </div> </div> ); } return ( <TopBanner /> ) } const domContainer = document.querySelector('#mount'); const root = ReactDOM.createRoot(domContainer); root.render(<App/>);
关键改动说明
- 将输入框改为受控组件:用
value={gridLine.hours}替代defaultValue,确保输入框与状态双向同步。 - 解决闭包问题:通过组件外部化或
useCallback,让addLine函数始终引用最新的gridLine状态。 - 修正DOM挂载错误:原代码中选择器
#like_button_container与HTML中的#mount不匹配,已统一修正。
内容的提问来源于stack exchange,提问作者Pete
相关产品推荐
相关产品推荐

