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

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/>);
关键改动说明
  1. 将输入框改为受控组件:用value={gridLine.hours}替代defaultValue,确保输入框与状态双向同步。
  2. 解决闭包问题:通过组件外部化或useCallback,让addLine函数始终引用最新的gridLine状态。
  3. 修正DOM挂载错误:原代码中选择器#like_button_container与HTML中的#mount不匹配,已统一修正。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 05:36:27