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

Redux跨组件触发表单更新异常问题求助

React表单更新提交异常

我有Form和List两个组件:Form用于注册新用户(包含nom、prenom、email、password字段),List展示通过Form添加的用户列表。列表表格显示用户ID、姓名、邮箱,附带更新和删除按钮,删除功能正常,但更新功能存在异常:

  • 点击更新按钮会调用Redux store的action,通过axios从数据库获取选中用户的完整信息(含ID)
  • store中userToUpdate状态更新后,Form组件重新渲染并正确显示该用户信息
  • 但立即点击提交时,表单提示所有字段为空;点击两次更新按钮再提交,表单会提交第一次点击的用户数据;点击表单输入框触发onChange更新本地状态后再提交,错误消失且数据正确

请问为何store更新后表单数据显示正常,但需点击两次更新按钮或触发输入框onChange才能正常提交?

Form.js 代码

import axios from 'axios';
import React, { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useSelector, useDispatch } from 'react-redux';

function Form() {
    const [message, setMessage] = useState([]);
    const userToUpdate = useSelector(state => state.userToUpdate);
    const [formUser, setFormUser] = useState({...userToUpdate});

    useEffect(() => {
        setFormUser({...userToUpdate});
    }, [userToUpdate])
    
    const onChange = e => {
        setFormUser({
            ...formUser,
            [e.target.id]: e.target.value
        })
    }

    const empty = e => {
            e.target.value = "";
    }

    const { register, handleSubmit, watch, formState: { errors } } = useForm();
    const onSubmit = (data) => {
        console.log(data);
        axios.post("http://localhost:8080/", data)
        .then(response => {
            setMessage([{
                type: "success",
                content: response.data
            }]);
        })
        .catch(error => {
            setMessage(error.response.data);
            setMessage([{
                type: "danger",
                content: error.response.data
            }]);
        });
    }

    return (
        <form className='w-25 p-4 mx-auto my-3 border border-success rounded' onSubmit={handleSubmit(onSubmit)}>
            <div className="mb-3">
                <label htmlFor="nom" className="form-label">Nom</label>
                <input type="text" className={"form-control" + (errors.nom ? " is-invalid" : "")} id="nom" {...register("nom", {required: true, maxLength: 30})} value={formUser.nom} onChange={onChange} />
                {errors.nom && <span className='text-danger'><em>Nom</em> est requis (30 caractères max.)</span>}
            </div>
            <div className="mb-3">
                <label htmlFor="prenom" className="form-label">Prénom</label>
                <input type="text" className={"form-control" + (errors.prenom ? " is-invalid" : "")} id="prenom" {...register("prenom", {required: true, maxLength: 30})} value={formUser.prenom} onChange={onChange} />
                {errors.prenom && <span className='text-danger'><em>Prénom</em> est requis (30 caractères max.)</span>}
            </div>
            <div className="mb-3">
                <label htmlFor="email" className="form-label">Email</label>
                <input type="email" className={"form-control" + (errors.email ? " is-invalid" : "")} id="email" {...register("email", {required: true, maxLength: 50})} value={formUser.email} onChange={onChange} />
                {errors.email && <span className='text-danger'><em>Email</em> est requis (50 caractères max.)</span>}
            </div>
            <div className="mb-3">
                <label htmlFor="password" className="form-label">Password</label>
                <input type="password" className={"form-control" + (errors.password ? " is-invalid" : "")} id="password" {...register("password", {required: true, maxLength: 30})} value={formUser.password} onChange={onChange} onClick={(event) => {if(event.target.value.length > 30){empty(event)}}} />
                {errors.password && <span className='text-danger'><em>Password</em> est requis (30 caractères max.)</span>}
            </div>
            <button type="submit" className="btn btn-primary">Submit</button>
            {message.length > 0 ? <> <div className={'alert mt-3 mb-0 alert-' + (message && message[0].type)}>{message[0].content}<a type="button" href="/" className={'d-block mx-auto mt-2 btn btn-' + (message && message[0].type)}>Recharger</a></div> </> : <> </>}
        </form>
    )
}

export default Form

List.js 代码

import axios from 'axios'
import React, { useState, useEffect } from 'react'
import { useDispatch } from 'react-redux';
import { actionCreators } from "../redux/index";
import { bindActionCreators } from 'redux';

function List() {
    const [usersList, setUsersList] = useState([]);
    const dispatch = useDispatch();
    const AC = bindActionCreators(actionCreators, dispatch);

    useEffect(() => {
        getUsersList();
    },[]);

    const getUsersList = async() => {
        const getUsers = await axios.get("http://localhost:8080/");
        setUsersList(getUsers.data);
    }

    const deleteUser = async(id) => {
        console.log(id);
        axios.delete("http://localhost:8080/", {params: {id: id}})
        .then(response => {
            getUsersList();
        })
        .catch(error => {

        });
    }

    return (
        <div className='w-50 p-4 mx-auto my-3 border border-success rounded'>
            <h2>Liste des utilisateurs</h2>
            {usersList.length <= 0 ? <h4 className='text-center'>Aucun utilisateur</h4> : 
                <table className="table table-striped">
                    <thead>
                        <tr>
                            <th scope="col">#</th>
                            <th scope="col">Nom</th>
                            <th scope="col">Prenom</th>
                            <th scope="col">Email</th>
                            <th scope="col">Upd.</th>
                            <th scope="col">Supp</th>
                        </tr>
                    </thead>
                    <tbody>
                        {usersList.map(element => {
                            return (
                                <tr className='align-middle' key={element.id}>
                                    <th scope="row">{element.id}</th>
                                    <td>{element.nom}</td>
                                    <td>{element.prenom}</td>
                                    <td>{element.email}</td>
                                    <td><a className='btn btn-warning' onClick={() => AC.updateUser(element.id)}><i className="bi bi-pencil-fill"></i></a></td>
                                    <td><a className='btn btn-danger' onClick={() => deleteUser(element.id)}><i className="bi bi-trash3-fill"></i></a></td>
                                </tr>
                            )
                        })}
                    </tbody>
                </table>
            }
        </div>
        
    )
}

export default List

已尝试的解决方案

  • 将store数据存入本地状态
  • 使用扩展运算符复制store数据到表单本地状态
  • 等待setFormUser完成再结束useEffect

以上方法均未解决问题,如需更多信息请告知。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 07:18:11