React useReducer用户信息表单无法修改输入值的问题求助
我是首次提问,感谢您的帮助。我接触MERN栈已有数周,在开发用户查看/修改个人信息表单时遇到问题:通过useEffect钩子结合axios的GET请求获取当前用户信息,调用reducer的dispatch更新状态后,表单能正确自动填充信息,但无法手动修改输入值。以下是我的代码:
import React, { useContext, useEffect, useReducer } from 'react'; import { Helmet } from 'react-helmet-async'; import Form from 'react-bootstrap/Form'; import { Resalia } from '../Resalia'; import Button from 'react-bootstrap/Button'; import { toast } from 'react-toastify'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; import { getError } from '../utils'; import LoadingBox from '../components/LoadingBox'; import MessageBox from '../components/MessageBox'; const reducer = (state, action) => { switch (action.type) { case 'FIELD': return { ...state, [action.fieldName]: action.payload }; case 'FETCH_REQUEST': return { ...state, loading: true }; case 'FETCH_SUCCESS': return { ...state, user: action.payload, loading: false }; case 'FETCH_FAIL': return { ...state, error: action.payload, loading: false }; default: return state; } }; export default function AdminPersonalInfoScreen() { const navigate = useNavigate(); const { state } = useContext(Resalia); const { userInfo } = state; const { email } = userInfo; const [{ user, error, loading }, dispatch] = useReducer(reducer, { loading: true, user: {}, error: '', }); const { name, surname, phone1, phone2, address1, address2, city, province, postcode, } = user; useEffect(() => { if (!userInfo) { navigate('/signin?redirect=/admin/personal-info'); } }, [userInfo, navigate]); useEffect(() => { const fetchData = async () => { dispatch({ type: 'FETCH_REQUEST' }); try { const result = await axios.get( `/api/users/personal-info/${userInfo._id}` ); dispatch({ type: 'FETCH_SUCCESS', payload: result.data }); } catch (err) { dispatch({ type: 'FETCH_FAIL', payload: err.message }); } }; fetchData(); }, [userInfo._id]); const submitHandler = async (e) => { e.preventDefault(); try { const { data } = await axios.put( '/api/users/personal-info', { email, name, surname, phone1, phone2, address1, address2, city, province, postcode, }, { headers: { Authorization: `Bearer ${userInfo.token}` }, } ); localStorage.setItem('userInfo', JSON.stringify(data)); toast.success('Datos guardados con éxito'); } catch (err) { toast.error(getError(err)); } }; return ( <div> <Helmet> <title>Mis Datos Personales</title> </Helmet> <h2 className="mt-4 mb-5">Mis Datos Personales</h2> {loading ? ( <LoadingBox /> ) : error ? ( <MessageBox variant="danger">{error}</MessageBox> ) : ( <Form onSubmit={submitHandler}> <Form.Group className="mb-4" controlId="name"> <Form.Label>Nombre</Form.Label> <Form.Control type="text" required value={user.name} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'name', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="surname"> <Form.Label>Apellidos</Form.Label> <Form.Control type="text" value={surname} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'surname', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="email"> <Form.Label>Email</Form.Label> <Form.Control type="email" value={email} required disabled /> </Form.Group> <Form.Group className="mb-4" controlId="phone1"> <Form.Label>Teléfono de Contacto</Form.Label> <Form.Control type="phone" value={phone1} required onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'phone1', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="phone2"> <Form.Label>Teléfono Adicional</Form.Label> <Form.Control type="phone" value={phone2} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'phone2', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="address1"> <Form.Label>Dirección (nombre de la vía y número)</Form.Label> <Form.Control type="text" value={address1} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'address1', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="address2"> <Form.Label>Dirección 2 (piso y puerta si procede)</Form.Label> <Form.Control type="text" value={address2} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'address2', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="city"> <Form.Label>Municipio</Form.Label> <Form.Control type="text" value={city} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'city', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="province"> <Form.Label>Provincia</Form.Label> <Form.Control type="text" value={province} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'province', payload: e.currentTarget.value, }); }} /> </Form.Group> <Form.Group className="mb-4" controlId="postcode"> <Form.Label>Código Postal</Form.Label> <Form.Control type="text" value={postcode} onChange={(e) => { dispatch({ type: 'FIELD', fieldName: 'postcode', payload: e.currentTarget.value, }); }} /> </Form.Group> <div className="mb-3"> <Button variant="primary" type="submit"> Guardar Cambios </Button> </div> </Form> )} </div> ); }
请问如何实现既能获取用户当前信息,又允许手动修改输入值?我尝试自行修复并查找相关问题但未成功,恳请帮助。
问题根源
你的reducer中FIELD类型的action是直接更新state的顶层属性(比如name、surname),但表单输入框绑定的是user对象下的属性(如user.name),两者没有关联。当你dispatch修改字段时,state的顶层属性变化了,但user对象里的属性并没有更新,所以输入框的值始终保持初始获取的用户信息,无法响应手动修改。
修复步骤
修改Reducer的FIELD处理逻辑
更新FIELDaction,让它修改user对象内部的对应字段,而不是state的顶层属性:const reducer = (state, action) => { switch (action.type) { case 'FIELD': return { ...state, user: { ...state.user, [action.fieldName]: action.payload } }; case 'FETCH_REQUEST': return { ...state, loading: true }; case 'FETCH_SUCCESS': return { ...state, user: action.payload, loading: false }; case 'FETCH_FAIL': return { ...state, error: action.payload, loading: false }; default: return state; } };确保表单输入值与更新后的user属性绑定
你的输入框已经正确绑定了user.name、user.surname等属性,这一步不需要额外修改,但要确保解构的变量也是从user对象中获取的(你当前的代码已经做到了):const { name, surname, phone1, phone2, address1, address2, city, province, postcode, } = user;可选:优化初始状态避免undefined
可以给初始的user对象设置默认空字符串,避免输入框初始值为undefined:const [{ user, error, loading }, dispatch] = useReducer(reducer, { loading: true, user: { name: '', surname: '', phone1: '', phone2: '', address1: '', address2: '', city: '', province: '', postcode: '' }, error: '', });
修复原理
修改后的FIELD action会先复制原有的user对象,再更新指定字段,保证user对象的引用发生变化,触发组件重新渲染,输入框就能正确显示手动修改后的值。同时,表单提交时使用的解构变量(name、surname等)也会同步更新为最新值,确保提交数据正确。
内容的提问来源于stack exchange,提问作者Ángel Castillo

