英文:
Why am I getting the following warning after submitting a form using Formik: A component is changing a controlled input to be uncontrolled
问题
I am getting the following warning when submitting a form using Formik: "A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component."
I know that there are multiple questions already answered about this warning and I generally know what it means. But I can just not understand where it happens, where or when does the input become uncontrolled.
I already tried to delete formik.resetForm()
and formik.setFieldValue()
within the handleRegister
function because I thought maybe they are setting an input to undefined, but it did not change anything.
This is the code of my register component:
const Register: NextPage = () => {
// ... (rest of the code)
}
I hope this helps you with your issue.
英文:
I am getting the following warning when submitting a form using Formik: "A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component."
I know that there are multiple questions already answered about this warning and I generally know what it means. But I can just not understand where it happens, where or when does the input become uncontrolled.
I already tried to delete formik.resetForm()
and formik.setFieldValue()
within the handleRegister function because I thought maybe they are setting an input to undefined, but it did not change anything.
This is the code of my register component:
const Register: NextPage = () => {
const { data: session, status } = useSession()
if (session) {
const router = useRouter()
router.push('/')
}
const [error, setError] = useState('')
const [success, setSuccess] = useState('')
const handleRegister = async (values: RegisterInputsData) => {
setError('')
setSuccess('')
const registerInputsData = {
...values
}
if (!registerInputsData.company.website) {
delete registerInputsData.company.website
}
if (!registerInputsData.company.socials) {
delete registerInputsData.company.socials
}
const registerInputsDataJSON = JSON.stringify(registerInputsData)
const endpoint: RequestInfo = '/api/register'
const options: RequestInit = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: registerInputsDataJSON
}
try {
const response: Response = await fetch(endpoint, options)
if (!response.ok) {
const { error } = await response.json()
if (typeof error === 'object' && error.name === 'ValidationError') {
const formikError = yupToFormErrors(error)
return formik.setErrors(formikError)
}
return setError(error)
}
formik.resetForm()
setSuccess(registerInputsData.user.email)
} catch (error) {
setError('Register failed')
} finally {
formik.setFieldValue('user.password', formik.initialValues.user.password, false)
formik.setFieldValue('user.passwordConfirmation', formik.initialValues.user.passwordConfirmation, false)
formik.setFieldValue('user.pin', formik.initialValues.user.pin, false)
formik.setFieldValue('user.pinConfirmation', formik.initialValues.user.pinConfirmation, false)
}
}
const formik = useFormik({
initialValues: {
user: {
email: '',
password: '',
passwordConfirmation: '',
pin: '',
pinConfirmation: ''
},
company: {
name: '',
title: '',
firstName: '',
lastName: '',
street: '',
houseNumber: '',
postcode: '',
city: '',
country: '',
countryCode: '',
callNumber: '',
email: '',
website: '',
socials: ''
}
},
validationSchema: registerYupSchema,
onSubmit: async (values) => {
await handleRegister(values)
}
})
return status === 'loading' ? <></> : (
<>
<Navbar />
<form onSubmit={formik.handleSubmit} noValidate className={styles.form}>
<div className={styles.section}>
<h3 className={styles.h3}>User</h3>
<div className={styles.group}>
<input
name='user.email'
type='text'
value={formik.values.user.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Email<span>*</span></label>
{formik.touched.user?.email && formik.errors.user?.email && <div className={styles.error}>{formik.errors.user.email}</div>}
</div>
<div className={styles.group}>
<input
name='user.password'
type='password'
value={formik.values.user.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Password<span>*</span></label>
{formik.touched.user?.password && formik.errors.user?.password && <div className={styles.error}>{formik.errors.user.password}</div>}
</div>
<div className={styles.group}>
<input
name='user.passwordConfirmation'
type='password'
value={formik.values.user.passwordConfirmation}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Password Confirmation<span>*</span></label>
{formik.touched.user?.passwordConfirmation && formik.errors.user?.passwordConfirmation && <div className={styles.error}>{formik.errors.user.passwordConfirmation}</div>}
</div>
<div className={styles.group}>
<input
name='user.pin'
type='password'
value={formik.values.user.pin}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Pin<span>*</span></label>
{formik.touched.user?.pin && formik.errors.user?.pin && <div className={styles.error}>{formik.errors.user.pin}</div>}
</div>
<div className={styles.group}>
<input
name='user.pinConfirmation'
type='password'
value={formik.values.user.pinConfirmation}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Pin Confirmation<span>*</span></label>
{formik.touched.user?.pinConfirmation && formik.errors.user?.pinConfirmation && <div className={styles.error}>{formik.errors.user.pinConfirmation}</div>}
</div>
</div>
<div className={styles.section}>
<h3 className={styles.h3}>Company</h3>
<div className={styles.group}>
<input
name='company.name'
type='text'
value={formik.values.company.name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Company Name<span>*</span></label>
{formik.touched.company?.name && formik.errors.company?.name && <div className={styles.error}>{formik.errors.company.name}</div>}
</div>
<div className={styles.group}>
<select
name='company.title'
value={formik.values.company.title}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.select}
aria-label='Title'
>
<option value='' hidden className={styles.option}></option>
<option value='Ms.' className={styles.option}>Ms.</option>
<option value='Mr.' className={styles.option}>Mr.</option>
</select>
<label className={styles.label}>Title<span>*</span></label>
{formik.touched.company?.title && formik.errors.company?.title && <div className={styles.error}>{formik.errors.company.title}</div>}
</div>
<div className={styles.group}>
<input
name='company.firstName'
type='text'
value={formik.values.company.firstName}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>First Name<span>*</span></label>
{formik.touched.company?.firstName && formik.errors.company?.firstName && <div className={styles.error}>{formik.errors.company.firstName}</div>}
</div>
<div className={styles.group}>
<input
name='company.lastName'
type='text'
value={formik.values.company.lastName}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Last Name<span>*</span></label>
{formik.touched.company?.lastName && formik.errors.company?.lastName && <div className={styles.error}>{formik.errors.company.lastName}</div>}
</div>
<div className={styles.group}>
<input
name='company.street'
type='text'
value={formik.values.company.street}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Street<span>*</span></label>
{formik.touched.company?.street && formik.errors.company?.street && <div className={styles.error}>{formik.errors.company.street}</div>}
</div>
<div className={styles.group}>
<input
name='company.houseNumber'
type='text'
value={formik.values.company.houseNumber}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>House Number<span>*</span></label>
{formik.touched.company?.houseNumber && formik.errors.company?.houseNumber && <div className={styles.error}>{formik.errors.company.houseNumber}</div>}
</div>
<div className={styles.group}>
<input
name='company.postcode'
type='text'
value={formik.values.company.postcode}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Postcode<span>*</span></label>
{formik.touched.company?.postcode && formik.errors.company?.postcode && <div className={styles.error}>{formik.errors.company.postcode}</div>}
</div>
<div className={styles.group}>
<input
name='company.city'
type='text'
value={formik.values.company.city}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>City<span>*</span></label>
{formik.touched.company?.city && formik.errors.company?.city && <div className={styles.error}>{formik.errors.company.city}</div>}
</div>
<div className={styles.group}>
<select
name='company.country'
value={formik.values.company.country}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.select}
aria-label='Country'
>
<option value='' hidden className={styles.option}></option>
{Object.keys(countries).map((key, i) => (
<option value={countries[key].name} key={i} className={styles.option}>{countries[key].name}</option>
))}
</select>
<label className={styles.label}>Country<span>*</span></label>
{formik.touched.company?.country && formik.errors.company?.country && <div className={styles.error}>{formik.errors.company.country}</div>}
</div>
<div className={styles.group}>
<select
name='company.countryCode'
value={formik.values.company.countryCode}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.select}
aria-label='Country Code'
>
<option value='' hidden className={styles.option}></option>
{Object.keys(countries).map((key, i) => (
<option value={'+' + countries[key].phone} key={i} className={styles.option}>{countries[key].emoji + ' +' + countries[key].phone}</option>
))}
</select>
<label className={styles.label}>Country Code<span>*</span></label>
{formik.touched.company?.countryCode && formik.errors.company?.countryCode && <div className={styles.error}>{formik.errors.company.countryCode}</div>}
</div>
<div className={styles.group}>
<input
name='company.callNumber'
type='text'
value={formik.values.company.callNumber}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Call Number<span>*</span></label>
{formik.touched.company?.callNumber && formik.errors.company?.callNumber && <div className={styles.error}>{formik.errors.company.callNumber}</div>}
</div>
<div className={styles.group}>
<input
name='company.email'
type='text'
value={formik.values.company.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Email<span>*</span></label>
{formik.touched.company?.email && formik.errors.company?.email && <div className={styles.error}>{formik.errors.company.email}</div>}
</div>
<div className={styles.group}>
<input
name='company.website'
type='text'
value={formik.values.company.website}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Website</label>
{formik.touched.company?.website && formik.errors.company?.website && <div className={styles.error}>{formik.errors.company.website}</div>}
</div>
<div className={styles.group}>
<input
name='company.socials'
type='text'
value={formik.values.company.socials}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
className={styles.input}
/>
<label className={styles.label}>Socials</label>
{formik.touched.company?.socials && formik.errors.company?.socials && <div className={styles.error}>{formik.errors.company.socials}</div>}
</div>
</div>
<button type='submit' disabled={formik.isSubmitting} className={styles.button}>Register</button>
</form>
{formik.isSubmitting && <div className={styles.lds_ring}><div></div><div></div><div></div><div></div></div>}
{!formik.isSubmitting && error && <div className={styles.message}>{error}</div>}
{!formik.isSubmitting && success && <div className={styles.message}>Registration successful - A verification email has been sent to the following email: {success}</div>}
</>
)
}
export default Register
I am thankful for any help I can get.
答案1
得分: 1
这种情况下的"values"参数是一个直接引用对象的引用。虽然你在创建它的深拷贝时使用了展开操作符,但由于"values"中包含嵌套对象,你需要在这些嵌套对象上也使用展开操作符。否则它们将成为浅拷贝。
而且,如果删除它们的属性("website"、"socials")而相应的输入保持为空,这些输入将变得不受控制。
解决方案可以看起来像这样:
const registerInputsData = {
user: {...values.user},
company: {...values.company}
}
英文:
The values parameter in this case is a direct reference to an object. Although you are using the spread operators to create a deep copy of it, as there are nested objects within values you need to use the spread operator on these too. Otherwise they will be shallow copies.
And as you are deleting properties of them (website, socials) if the respecting inputs remain empty, the inputs become uncontrolled.
The solution could look like this:
const registerInputsData = {
user: {...values.user},
company: {...values.company}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论