React子组件Text无法更新父组件传递的useState默认值问题
React子组件Text无法同步父组件传递的value值
我有一个React页面,使用了带复制到剪贴板、字符数统计功能的Text组件。现在遇到的问题是:父组件EnterpriseInfo向Text组件传递value值时,Text组件内部的inputValue无法同步更新。
我试过直接把props的value作为defaultValue,但这样没法获取用户修改后的字符长度,也没法执行复制操作;还试过添加依赖为[value]的useEffect,但也没效果。
父组件EnterpriseInfo代码
import React, {useEffect, useRef, useState} from 'react' import {useNavigate, useParams} from 'react-router-dom' import {enterprise_info} from "../../config/AlertInfo"; import Header from '../../layouts/Header' import EnterpriseMenu from "../../pages/enterprises/EnterpriseMenu"; import GetPermissionsMessage from "../../hooks/GetPermissionsMessage"; import Horizontal from "../../components/navigation/horizontal"; import Preloader from "../../components/card/Preloader"; import Alert from "../../components/alert/Alert"; import Text from "../../components/inputs/Text"; import Email from "../../components/inputs/Email"; import Button from "../../components/inputs/Button"; import UseGetFormData from "../../hooks/UseGetFormData"; import UseRequest from "../../hooks/UseRequest" const EnterpriseInfo = () => { const {id} = useParams() const formRef = useRef() const history = useNavigate(); const [errors, setErrors] = useState({}) const [loading, setLoading] = useState(true) const [preloader, setPreloader] = useState({}) const [enterprise, setEnterprise] = useState({}) const fetchEnterprise = async () => { return await (await UseRequest( `/enterprises/get`, {request: {method: 'PUT', data: {limit: 1, offset: 0, _id: id}}} )).json(); } useEffect(() => { const message = GetPermissionsMessage('enterprises', 'upsert') if(message){ setPreloader({ title: "You don't have permissions", description: message }) } if (id != undefined) { fetchEnterprise().then((response) => { if(response?.entities?.length){ setEnterprise(response.entities[0]) setLoading(false) } }); } setLoading(false) }, []) const handleSave = async (ev) => { ev.preventDefault() const formData = UseGetFormData(formRef) const response = await (await UseRequest( `/enterprises/upsert/${id ?? ''}`, { request: { method: 'POST', data: { name : formData.get('name'), email : formData.get('email'), phone : formData.get('phone'), website: formData.get('website'), vat : formData.get('vat'), } } })).json(); if (response.errors) { setErrors(response.errors) } if (!id && response._id) { history(`/enterprises/edit/${response._id}`) } } return ( <div className="Enterprise"> <Header title="Enterprise Manager" icon="enterprise"/> <div className="card"> <Preloader loading={loading} title={preloader?.title} description={preloader.description} /> {id && <Horizontal {...EnterpriseMenu} />} <form ref={formRef} className="max-width-500"> <Alert icon="info" type="primary" message={enterprise_info}/> <Text required copy name="name" label="Name" error={errors.name} value={enterprise.name} placeholder="Type company name" /> <Email required name="email" label="Email" error={errors.email} value={enterprise.email} placeholder="Type company email" /> <Text required name="phone" label="Phone" error={errors.phone} value={enterprise.phone} placeholder="Type company phone" /> <Text name="website" label="Website" error={errors.website} value={enterprise.website} placeholder="Type company website" /> <Text name="vat" label="Vat number" error={errors.vat} value={enterprise.vat} placeholder="Type vat number" /> <Button type="primary" label="Save" icon="check" onClick={handleSave} /> </form> </div> </div> ) } export default EnterpriseInfo;
Text组件代码
import React, {useState} from "react"; import Icon from "../icon/Icon"; import UseCopyToClipboard from "../../hooks/UseCopyToClipboard"; const Text = (props) => { const { name, label, value, error, counter, copy, required, placeholder, className, onChange, onBlur, ...rest } = props const [inputValue, setInputValue] = useState(value) const handleChange = (ev) => { setInputValue(ev.target.content) if (typeof onChange == "function") { onChange(ev) } } const handleCopy = () => { UseCopyToClipboard(inputValue) } return ( <React.Fragment> <div className="form-group"> {label && <label className="Label"> {required && <span className="text-red required">* </span>}{label} {error && <span className="error">{error}</span>} </label>} <div className="Input input-container"> <input name={name} type="text" autoComplete="off" value={inputValue} onChange={handleChange} placeholder={placeholder} className={`form-control ${className ?? ''}`} {...rest} /> {counter && <span className="counter">{inputValue.length}</span>} {copy && <span className="copy" onClick={handleCopy}><Icon name="copy"/></span>} </div> </div> </React.Fragment> ) } export default Text;
问题原因
- 初始化值仅生效一次:Text组件中
useState(value)只会在组件挂载时读取一次初始值,后续父组件传递的props.value变化不会自动同步到inputValue。 - 输入值获取错误:
handleChange里错误使用了ev.target.content,正确的输入值应该是ev.target.value,这会导致用户输入时inputValue也无法正确更新。
修复方案
方案一:受控组件模式(推荐)
既然父组件已经传递了value,Text组件应该作为受控组件,通过useEffect监听props.value的变化,同步更新内部状态;同时修正输入值的获取逻辑:
import React, { useState, useEffect } from "react"; import Icon from "../icon/Icon"; import UseCopyToClipboard from "../../hooks/UseCopyToClipboard"; const Text = (props) => { const { name, label, value, error, counter, copy, required, placeholder, className, onChange, onBlur, ...rest } = props const [inputValue, setInputValue] = useState(value); // 监听props.value变化,同步更新内部状态 useEffect(() => { setInputValue(value); }, [value]); const handleChange = (ev) => { const newValue = ev.target.value; // 修正为正确的输入值获取方式 setInputValue(newValue); if (typeof onChange === "function") { onChange(newValue, ev); // 将新值传递给父组件的回调 } }; const handleCopy = () => { UseCopyToClipboard(inputValue); }; return ( <div className="form-group"> {label && ( <label className="Label"> {required && <span className="text-red required">* </span>} {label} {error && <span className="error">{error}</span>} </label> )} <div className="Input input-container"> <input name={name} type="text" autoComplete="off" value={inputValue} onChange={handleChange} placeholder={placeholder} className={`form-control ${className ?? ""}`} {...rest} /> {counter && <span className="counter">{inputValue.length}</span>} {copy && ( <span className="copy" onClick={handleCopy}> <Icon name="copy" /> </span> )} </div> </div> ); }; export default Text;
同时,父组件需要添加onChange回调来更新自身状态(如果需要双向绑定):
<Text required copy name="name" label="Name" error={errors.name} value={enterprise.name} placeholder="Type company name" onChange={(value) => setEnterprise(prev => ({...prev, name: value}))} />
方案二:仅修正输入值获取(非受控场景)
如果父组件只需要初始化值,后续让Text组件独立维护状态,只需修正handleChange中的错误:
const handleChange = (ev) => { setInputValue(ev.target.value); // 替换ev.target.content为ev.target.value if (typeof onChange == "function") { onChange(ev); } };
注意:这种方式下,父组件后续更新value时,Text组件不会同步,仅适用于不需要父组件控制后续值的场景。
内容的提问来源于stack exchange,提问作者Infobuscador
相关产品推荐
相关产品推荐

