React子组件People加载时state未更新问题排查求助
React组件状态与props不同步问题解决
问题场景
React应用包含MainScreen父组件,以及UserList和People两个子组件。点击UserList中的用户名称时,触发MainScreen的prop函数更新传递给People组件的props,控制台显示props已正确设置,但People组件中绑定到输入框的fName、lName状态无法同步更新,输入框需支持可编辑。
问题代码(People组件)
import { useState } from 'react' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faXmark } from '@fortawesome/free-solid-svg-icons' import { faPlusCircle } from '@fortawesome/free-solid-svg-icons' function People(props){ console.log('Start People') console.log(props.userDet) console.log(props.userDet.FirstName) const users = props.userDet console.log('Users vairable') console.log(users.FirstName) console.log(props.userDet.hasOwnProperty('FirstName')) const [fName,setfName] = useState(users.FirstName || 'n') const [lName,setlName] = useState(users.LastName || 'n') console.log('fName') console.log(fName) console.log(props.userDet.FirstName) const handleChange = e => { console.log('handle change running') if(e.target.name === 'fName'){ setfName(e.target.value) } else{ setlName(e.target.value) } } function formatPhoneNumber(phoneNumberString) { var cleaned = ('' + phoneNumberString).replace(/\D/g, ''); var match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/); if (match) { return '(' + match[1] + ') ' + match[2] + '-' + match[3]; } return null; } var phoness = props.userDet.phones; const onephone = phoness.map((phone,index) => <div className={(index % 2) ? 'greyBg'+ " phonehold" : 'noBg'+ " phonehold"} id={"phone_"+phone.phone_id} > <div>{phone.phone_type}</div> <div>{formatPhoneNumber.call(this, phone.phone_number)}</div> <div><FontAwesomeIcon icon={faXmark}/></div> </div>) return (<div id="peopledets"> <div id="peoinfo"> <div id="peonames"> <div className="header">Person Details</div> <div className="holder"><div className="formlabel">First Name:</div><div><input onChange={handleChange} name="fName" type="text" value={fName} /></div></div> <div className="holder"><div className="formlabel">Last Name:</div><div><input onChange={handleChange} name="lName" type="text" value={lName} /></div></div> </div> <div id="peophones"> <div className="header">Person Phones<span style={{marginLeft:'5px',fontSize:'.8em'}}><FontAwesomeIcon icon={faPlusCircle}/></span></div> {onephone} </div> </div> </div>) } export default People;
问题原因
useState的初始值仅在组件首次挂载时生效,后续父组件传递新的props.userDet时,组件会重新渲染,但useState不会再次读取新的props值来更新状态,导致fName、lName始终保持第一次初始化的内容,无法同步最新的props数据。
解决方案
方案1:使用useEffect监听props变化更新状态
通过useEffect监听props.userDet的变化,当props更新时手动更新本地状态:
import { useState, useEffect } from 'react' // 保留原有导入和其他代码 function People(props){ const [fName,setfName] = useState(props.userDet.FirstName || 'n') const [lName,setlName] = useState(props.userDet.LastName || 'n') // 添加监听props变化的effect useEffect(() => { setfName(props.userDet.FirstName || 'n') setlName(props.userDet.LastName || 'n') }, [props.userDet]) // 依赖项为props.userDet,当它变化时执行回调 // 原有handleChange和其他代码保持不变 }
方案2:直接使用props作为受控组件值(无需本地状态)
如果输入框的修改需要实时同步到父组件,可以去掉本地状态,直接使用props的值,并通过回调通知父组件更新数据:
function People(props){ const handleChange = e => { // 构造更新后的用户数据 const updatedUser = { ...props.userDet, [e.target.name === 'fName' ? 'FirstName' : 'LastName']: e.target.value } // 调用父组件传递的回调函数更新数据 props.onUserUpdate(updatedUser) } // 原有formatPhoneNumber函数保持不变 var phoness = props.userDet.phones; const onephone = phoness.map((phone,index) => ( <div className={(index % 2) ? 'greyBg phonehold' : 'noBg phonehold'} id={"phone_"+phone.phone_id} > <div>{phone.phone_type}</div> <div>{formatPhoneNumber(phone.phone_number)}</div> <div><FontAwesomeIcon icon={faXmark}/></div> </div> )) return (<div id="peopledets"> <div id="peoinfo"> <div id="peonames"> <div className="header">Person Details</div> <div className="holder"> <div className="formlabel">First Name:</div> <div><input onChange={handleChange} name="fName" type="text" value={props.userDet.FirstName || 'n'} /></div> </div> <div className="holder"> <div className="formlabel">Last Name:</div> <div><input onChange={handleChange} name="lName" type="text" value={props.userDet.LastName || 'n'} /></div> </div> </div> <div id="peophones"> <div className="header">Person Phones<span style={{marginLeft:'5px',fontSize:'.8em'}}><FontAwesomeIcon icon={faPlusCircle}/></span></div> {onephone} </div> </div> </div>) }
注意:使用此方案时,需要在父组件MainScreen中传递onUserUpdate回调函数,用于接收更新后的用户数据并更新父组件状态。
内容的提问来源于stack exchange,提问作者MW_DEVEL
相关产品推荐
相关产品推荐

