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

React状态对象中修改数组元素时Input失焦问题求助

解决React输入框输入时失焦的问题

问题原因

输入框每次输入字符就失焦,核心原因是组件定义在父组件的渲染函数内部:EditFieldRow、EditField、PageContent这三个组件都定义在GetProgram组件内部。每当GetProgram的状态(比如exercises)更新时,父组件会重新渲染,这三个组件会被重新定义为全新的组件类型。React会卸载旧的组件实例,再挂载新的,导致输入框失去焦点。

另外,updateField函数直接引用旧的exercises状态快照,可能导致状态更新不及时,加剧组件重复渲染的问题。


修复步骤

1. 将内部组件移到父组件外部

把EditFieldRow、EditField、PageContent从GetProgram组件内部移出,确保它们不会在每次父组件渲染时被重新定义:

// 移到GetProgram组件外部
const EditFieldRow = ({ set, reps, weight, onUpdate }) => {
    return (
        <tr>
            <td>{set}</td>
            <td><input type="text" value={reps} onChange={(e) => onUpdate({ set, reps: e.target.value })}/></td>
            <td><input type="text" value={weight} onChange={(e) => onUpdate({ set, weight: e.target.value })}/></td>
        </tr>
    )
}

const EditField = ({ exercise, j, onUpdate }) => {
    return (
        <div>
            <form onSubmit={(e) => {e.preventDefault(); console.log("submitted")}}>
            <table className="table table-bordered table-colored">
                <thead>
                    <tr>
                        <th>Set</th>
                        <th>Reps</th>
                        <th>Weight</th>
                    </tr>
                </thead>
                <tbody>
                    {[...Array(exercise.sets).keys()].map((i) => {
                        return (
                            <EditFieldRow 
                                key={`fieldrow-${j}-${i}`} 
                                set={i+1} 
                                reps={exercise.reps[i]} 
                                weight={exercise.weight[i]}
                                onUpdate={(data) => onUpdate({...data, j})}
                            />
                        )
                    })}
                </tbody>
            </table>
            </form>
        </div>
    )
}

const PageContent = ({ program, exercises, editingExerciseIndex, onEditExercise, onUpdateField }) => {
    return (
        <div className="container-fluid page-content program-page" >
            <h2 style={{textAlign:'center'}}>{program.rname??program.name} Program</h2>
            <div className="row">
                {program.dayMap.map((day, i) => {
                    return (
                        <div className="col" key={`${day}-${i}`}>
                            <h4>{day}</h4>
                            <hr />
                            <table className="lift-table table table-bordered table-colored">
                                <thead>
                                    <tr>
                                    <th>Name</th>
                                    <th>Sets</th>
                                    </tr>
                                </thead>
                                <tbody>
                                {exercises.map((exercise, j) => {
                                    if (exercise.day === i+1) {
                                        return (
                                            <React.Fragment key={`exercise-${exercise.position}-${j}`}>
                                                <tr 
                                                    id={`exercise-row-${exercise.name.replaceAll(" ", "-")}`} 
                                                    className={`exercise-row`} 
                                                    onClick={() => onEditExercise(j)}
                                                >
                                                    <td>{exercise.name}</td>
                                                    <td>{exercise.sets}</td>
                                                </tr>
                                                {editingExerciseIndex === j && (
                                                    <tr>
                                                        <td colSpan="2">
                                                            <EditField 
                                                                exercise={exercise} 
                                                                j={j}
                                                                onUpdate={onUpdateField}
                                                            />
                                                        </td>
                                                    </tr>
                                                )}
                                            </React.Fragment>
                                        )
                                    }
                                    return null;
                                })}
                                </tbody>
                            </table>
                        </div>
                    )
                })}
            </div>
        </div>
    )
}

// 原GetProgram组件
export default function GetProgram() {
    const [exercises, setExercises] = useState([])
    const [editingExerciseIndex, setEditingExerciseIndex] = useState(-1)
    const [loading, setLoading] = useState([true, true])
    const [program, setProgram] = useState({
        days: [],
        name : '',
        _id : null
    })

    useEffect(() => {
        async function getProgramInfo() {
            fetch(`http://localhost:5000/program`)
                .then(res => res.json())
                .then(body => {
                    setProgram(body)
                    setLoading([false, loading[1]])
                })
                .catch(err => console.log(`**ERR: ${err}`))
        }
        getProgramInfo()
    }, [])

    useEffect(() => {
        async function getExercises() {
            fetch(`http://localhost:5000/program/getmap`)
                .then(res => res.json())
                .then(body => {
                    setExercises(body)
                    setLoading([loading[0], false])
                })
        }
        getExercises()
    }, [])

    // 优化updateField,使用函数式更新获取最新状态
    const updateField = useCallback((props) => {
        setExercises(prevExercises => {
            const exerciseToUpdate = prevExercises[props.j];
            if (!exerciseToUpdate) return prevExercises;

            let newExercise;
            if (props.reps !== undefined) {
                const newReps = [...exerciseToUpdate.reps];
                newReps[props.set - 1] = parseInt(props.reps, 10) || 0;
                newExercise = {...exerciseToUpdate, reps: newReps};
            } else {
                const newWeight = [...exerciseToUpdate.weight];
                newWeight[props.set - 1] = parseInt(props.weight, 10) || 0;
                newExercise = {...exerciseToUpdate, weight: newWeight};
            }

            return prevExercises.map(exercise => 
                exercise.day === newExercise.day && exercise.position === newExercise.position
                    ? newExercise
                    : exercise
            );
        });
    }, []);

    if (program.dayMap) {
        return (
            <PageContent 
                program={program}
                exercises={exercises}
                editingExerciseIndex={editingExerciseIndex}
                onEditExercise={setEditingExerciseIndex}
                onUpdateField={updateField}
            />
        )
    }
    return <div></div>
}

2. 使用函数式状态更新

修改updateField函数,使用setExercises的函数式更新形式,确保每次基于最新状态修改,避免闭包捕获旧状态的问题。同时用useCallback包裹该函数,避免传递给子组件时触发不必要的重渲染。

3. 优化Key值

确保所有列表项的key稳定且唯一,比如使用exercise.position+索引,减少React的不必要重渲染。


额外优化点

  • 给form添加e.preventDefault(),避免提交时页面刷新
  • 将exercises.map放在单独的<tbody>里,避免生成多个<tbody>(不符合HTML规范)
  • 移除输入框的冗余key,父组件的key已足够区分不同行

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 07:15:31