英文:
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
-
问题可能出在您在
enterprise
加载之前显示组件,这意味着enterprise
的状态为{}
。结果是您传递给Text
的值是未定义的,inputValue
的初始状态也变为未定义。 -
有一些解决方法:
- 在
enterprise
有值之前不要渲染表单。
- 在
// 将初始状态设置为未定义
const [enterprise, setEnterprise] = useState(undefined)
{enterprise && <form ref={formRef} className="max-width-500">
- 删除子状态,只使用
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"
/>
- 为表单添加一个关键字,以在关键字更改时触发重新渲染。
<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:
- Dont render the form until
enterprise has a value
// Set initial state to undefined
const [enterprise, setEnterprise] = useState(undefined)
{enterprise && <form ref={formRef} className="max-width-500">
- Remove the child state and use the
enterprise
for everything by just passing a on change to the Text component that is used instead
<Text required copy
name="name"
label="Name"
error={errors.name}
value={enterprise.name}
onChange={(v)=> setEnterprise((curr)=> {...cur, name: v})}
placeholder="Type company name"
/>
- Add a key to the form to trigger a rerender on key change
<form key={enterprise.name}>
Final note: your handle change function could also be a factor.
You use setInputValue(ev.target.content)
change this to setInputValue(ev.target.value)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论