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

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;

问题原因

  1. 初始化值仅生效一次:Text组件中useState(value)只会在组件挂载时读取一次初始值,后续父组件传递的props.value变化不会自动同步到inputValue。
  2. 输入值获取错误: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 09:37:24