React useState with default value coming from parent component.

huangapple go评论92阅读模式
英文:

React useState with default value coming from parent component

问题

以下是 Text 组件的代码,其中 inputValue 没有更新。

我尝试直接使用通过参数传递的{value}字段作为defaultValue,但以这种方式操作后,我无法读取值的长度,也无法在用户修改后将其复制到剪贴板。

我还尝试在useEffect中将[value]作为依赖项,但也不起作用。

英文:

I have the following React page where I am calling a component that generates a text field with some extra functionality, such as: Copy the value to clipboard or display the number of characters. The problem I have is that when I try to pass the value from the page, it is not working.

The code of the page is this:

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;

Below is the code of the Text component where the inputValue is not updating.

I have tried to use directly the {value} field that comes by parameter as defaultValue but doing it this way, then I am not able to read the length of the value nor copy it to the clipboard once the user has modified it.

I also tried to call useEffect putting as dependency [value] but it doesn't work either.

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

得分: 2

  1. 问题可能出在您在enterprise加载之前显示组件,这意味着enterprise的状态为{}。结果是您传递给Text的值是未定义的,inputValue的初始状态也变为未定义。

  2. 有一些解决方法:

    1. enterprise有值之前不要渲染表单。
// 将初始状态设置为未定义
const [enterprise, setEnterprise] = useState(undefined)
{enterprise && <form ref={formRef} className="max-width-500">
  1. 删除子状态,只使用enterprise来处理一切,通过将更改传递给Text组件,而不是使用子状态。
<Text required copy
    name="name"
    label="Name"
    error={errors.name}
    value={enterprise.name}
    onChange={(v) => setEnterprise((curr) => ({...curr, name: v})}
    placeholder="Type company name"
/>
  1. 为表单添加一个关键字,以在关键字更改时触发重新渲染。
<form key={enterprise.name}>

最后注意:您的handleChange函数也可能是一个因素。您使用了setInputValue(ev.target.content),将其更改为setInputValue(ev.target.value)

英文:

The issue probably lays in that you show the components before the enterprise has loaded, which means that the state enterprise === {}. The result is that the value you pass down to the Text is undefined and the initial state of inputValue becomes undefined.

There is some solutions to this:

  1. Dont render the form until enterprise has a value
// Set initial state to undefined
const [enterprise, setEnterprise] = useState(undefined)
{enterprise &amp;&amp; &lt;form ref={formRef} className=&quot;max-width-500&quot;&gt;
  1. Remove the child state and use the enterprise for everything by just passing a on change to the Text component that is used instead
&lt;Text required copy
    name=&quot;name&quot;
    label=&quot;Name&quot;
    error={errors.name}
    value={enterprise.name}
    onChange={(v)=&gt; setEnterprise((curr)=&gt; {...cur, name: v})}
    placeholder=&quot;Type company name&quot;
/&gt;
  1. Add a key to the form to trigger a rerender on key change
&lt;form key={enterprise.name}&gt;

Final note: your handle change function could also be a factor.
You use setInputValue(ev.target.content) change this to setInputValue(ev.target.value)

huangapple
  • 本文由 发表于 2023年3月15日 19:28:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/75744075.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定