React子组件useEffect重置后加载数据不触发问题
问题背景
我有一段React表单代码,核心设计分层如下:
- FormContainer组件负责加载数据、处理表单提交逻辑、实现预填充数据加载能力,加载完成后将数据传递给子组件Form
- Form组件只负责所有表单渲染相关逻辑
对应实现代码如下:
import { Fragment, useEffect, useState } from "react"; const loadedState = { firstName:'Atul', lastName:'Joshi', fruits:'mango', isGoing:true } const initialState = { firstName:'', lastName:'', fruits:'', isGoing:false } const FormContainer = () => { const [userData,setUserData] = useState(initialState); const onSubmitHandler = formState =>{ console.log(formState); } const loadData = () =>{ console.log('setting user Dta',loadedState); setUserData(loadedState) } return( <Fragment> <Form onSubmit={onSubmitHandler} data={userData}></Form> <button type="button" onClick={loadData}>Load Data</button> </Fragment> ) } const Form = ({onSubmit,data}) => { const [formState,setFormState] = useState(initialState); useEffect(() => { console.log('form state with data ',data); // 点击重置按钮后这行日志不会触发 setFormState(data) },[data]); const onChange = e => { console.log(e.target.name); console.log(e.target.value); console.log(e.target.type); const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; setFormState({ ...formState, [e.target.name]:value }) } const onSubmitHandler = e => { e.preventDefault(); console.log('formState = ',formState) onSubmit(formState); } const init = e =>{ console.log('init..') setFormState(initialState) } return( <form onSubmit={onSubmitHandler}> <span>{`Your name Is - ${formState.firstName} ${formState.lastName}`}</span> <label htmlFor="firstName">FirstName</label> <input id="firstName" name="firstName" onChange={onChange} value={formState.firstName}/> <label htmlFor="lastName">LastName</label> <input id="lastName" name="lastName" onChange={onChange} value={formState.lastName}/> <label htmlFor="fruits">Fruits</label> <select id="fruits" name="fruits" value={formState.fruits} onChange={onChange}> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option value="coconut">Coconut</option> <option value="mango">Mango</option> </select> <label htmlFor="isGoing"> Is going: <input name="isGoing" type="checkbox" checked={formState.isGoing} onChange={onChange}/> </label> <button type="submit" onSubmit={onSubmitHandler}>Submit</button> <button type="button" onClick={init}>Reset</button> </form> ); } export default FormContainer;
问题现象
- 首次点击「Load Data」按钮时,表单可以正常填充预设值
- 点击「Reset」按钮重置表单后,后续再次点击「Load Data」按钮无法将数据加载到表单中
- 异常表现为:Form组件中
console.log('form state with data ',data)这条日志语句不会执行
问题原因
这个问题是两个逻辑问题叠加导致的:
- Reset逻辑只修改了子组件本地状态,没有同步父组件状态
点击Reset按钮触发的init方法,只会把Form组件内部的formState重置为初始值,不会通知父组件FormContainer把自身维护的userData状态重置。也就是说第一次点击Load Data之后,父组件的userData就一直是loadedState对象,再也没有变过。 - React状态更新做引用相等判断,相同引用不会触发重渲染
loadedState是组件外定义的全局常量,每次调用setUserData(loadedState)传入的都是同一个对象的引用。第一次调用时,userData从initialState变成loadedState,引用变化触发重渲染,传给Form的dataprop更新,依赖[data]的useEffect正常执行。
后续再点击Load Data时,React判断新旧userData是同一个对象引用,判定状态没有变化,不会触发组件重渲染,Form组件拿到的dataprop也不会更新,监听data的useEffect自然不会执行,日志也就不会打印,表单无法重新填充数据。
内容的提问来源于stack exchange,提问作者Atul
相关产品推荐
相关产品推荐

