Реакция - изменение неконтролируемого входа
У меня есть простой компонент реагирования с формой, которая, как мне кажется, имеет один контролируемый вход:
import React from 'react';
export default class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (
<form className="add-support-staff-form">
<input name="name" type="text" value={this.state.name} onChange={this.onFieldChange('name').bind(this)}/>
</form>
)
}
onFieldChange(fieldName) {
return function (event) {
this.setState({[fieldName]: event.target.value});
}
}
}
export default MyForm;
Когда я запускаю свое приложение, я получаю следующее предупреждение:
Предупреждение: MyForm изменяет неконтролируемый ввод текста типа для управления. Элементы ввода не должны переключаться с неконтролируемого на управляемый (или наоборот). Выберите между использованием контролируемого или неконтролируемого входного элемента в течение срока службы компонента.
Я считаю, что мой вклад контролируется, поскольку он имеет ценность. Мне интересно, что я делаю не так?
Я использую React 15.1.0
23 ответа
Я считаю, что мой вклад контролируется, поскольку он имеет ценность.
Для входа, который необходимо контролировать, его значение должно соответствовать значению переменной состояния.
Это условие изначально не выполняется в вашем примере, потому что this.state.name
изначально не установлен. Поэтому вход изначально не контролируется. Однажды onChange
обработчик запускается впервые, this.state.name
устанавливается В этот момент вышеуказанное условие удовлетворяется, и вход считается контролируемым. Этот переход от неконтролируемого к контролируемому приводит к ошибке, замеченной выше.
Инициализируя this.state.name
в конструкторе:
например
this.state = { name: '' };
вход будет контролироваться с самого начала, устраняя проблему. См. React Controlled Components для большего количества примеров.
Независимо от этой ошибки, у вас должен быть только один экспорт по умолчанию. Ваш код выше имеет два.
Когда вы впервые визуализируете свой компонент, this.state.name
не установлен, поэтому он оценивает undefined
и вы заканчиваете тем, что проходили value={undefined}
на ваш input
,
Когда ReactDOM проверяет, контролируется ли поле, он проверяет,value != null
(обратите внимание, что это !=
не !==
), и с тех пор undefined == null
в JavaScript он решает, что он не контролируется.
Так когда onFieldChange()
называется, this.state.name
установлен в строковое значение, ваш ввод из неконтролируемого к контролю.
Если вы делаете this.state = {name: ''}
в вашем конструкторе, потому что '' != null
ваш ввод будет иметь значение все время, и это сообщение исчезнет.
Другим подходом может быть установка значения по умолчанию внутри вашего ввода, например:
<input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>
Я знаю, что другие уже ответили на это. Но один из очень важных факторов, который может помочь другим людям, испытывающим подобные проблемы:
Вы должны иметь onChange
добавлен обработчик в поле ввода (например, textField, checkbox, radio и т. д.). И всегда занимайтесь активностью через onChange
обработчик, как:
<input ... onChange={ this.myChangeHandler} ... />
и когда вы работаете с флажком, то вам может понадобиться обработать его checked
состояние с !!
лайк:
<input type="checkbox" checked={!!this.state.someValue} onChange={.....} >
Простое решение для решения этой проблемы - установить пустое значение по умолчанию:
<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />
У меня такая же проблема. проблема была, когда я оставил информацию о состоянии пустой
const [name, setName] = useState()
Я исправил это, добавив пустую строку, подобную этой
const [name, setName] = useState('')
Один потенциальный недостаток при установке значения поля "" (пустая строка) в конструкторе - это если поле является необязательным и оставлено неотредактированным. Если вы не сделаете некоторый массаж перед отправкой формы, поле будет сохранено в вашем хранилище данных в виде пустой строки вместо NULL.
Эта альтернатива позволит избежать пустых строк:
constructor(props) {
super(props);
this.state = {
name: null
}
}
...
<input name="name" type="text" value={this.state.name || ''}/>
В моем случае мне не хватало чего-то действительно тривиального.
<input value={state.myObject.inputValue} />
Мое состояние было следующим, когда я получал предупреждение:
state = {
myObject: undefined
}
Чередуя мое состояние со ссылкой на ввод моей стоимости, моя проблема была решена:
state = {
myObject: {
inputValue: ''
}
}
Когда вы используете onChange={this.onFieldChange('name').bind(this)}
при вводе вы должны объявить пустую строку вашего состояния как значение поля свойства.
неверный путь:
this.state ={
fields: {},
errors: {},
disabled : false
}
правильный путь:
this.state ={
fields: {
name:'',
email: '',
message: ''
},
errors: {},
disabled : false
}
Если реквизиты вашего компонента были переданы как состояние, установите значение по умолчанию для ваших входных тегов.
<input type="text" placeholder={object.property} value={object.property ? object.property : ""}>
Установите значение для свойства name в начальном состоянии.
this.state={ name:''};
Просто создайте откат к '', если this.state.name имеет значение null.
<input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>
Это также работает с переменными useState.
Обновление для этого. Для React Hooks используйтеconst [name, setName] = useState(" ")
Я считаю, что мой ввод контролируется, поскольку он имеет значение.
Теперь вы можете сделать это двумя способами. Лучше всего иметь ключ состояния для каждого входа с 1 обработчиком onChange . Если у вас есть флажки, вам нужно будет написать отдельный обработчик onChange .
С компонентом класса вы хотели бы написать его так
import React from 'react';
export default class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {
myFormFields: {
name: '',
dob: '',
phone: ''
}
}
this.onFormFieldChange = this.onFormFieldChange.bind(this)
}
// Always have your functions before your render to keep state batches in sync.
onFormFieldChange(e) {
// No need to return this function can be void
this.setState({
myFormFields: {
...this.state.myFormFields,
[e.target.name]: e.target.value
}
})
}
render() {
// Beauty of classes we can destruct our state making it easier to place
const { myFormFields } = this.state
return (
<form className="add-support-staff-form">
<input name="name" type="text" value={myFormFields.name} onChange={this.onFormFieldChange}/>
<input name="dob" type="date" value={myFormFields.dob} onChange={this.onFormFieldChange}/>
<input name="phone" type="number" value={myFormFields.phone} onChange={this.onFormFieldChange}/>
</form>
)
}
}
export default MyForm;
Надеюсь, что это поможет классу, но самая эффективная и новейшая вещь, которую разработчики подталкивают всех использовать, — это функциональные компоненты . Это то, на что вы хотели бы ориентироваться, поскольку компоненты класса плохо переплетаются с последними библиотеками, поскольку все они теперь используют пользовательские хуки .
Написать как функциональный компонент
import React, { useState } from 'react';
const MyForm = (props) => {
// Create form initial state
const [myFormFields, setFormFields] = useState({
name: '',
dob: '',
phone: ''
})
// Always have your functions before your return to keep state batches in sync.
const onFormFieldChange = (e) => {
// No need to return this function can be void
setFormFields({
...myFormFields,
[e.target.name]: e.target.value
})
}
return (
<form className="add-support-staff-form">
<input name="name" type="text" value={myFormFields.name} onChange={onFormFieldChange}/>
<input name="dob" type="date" value={myFormFields.dob} onChange={onFormFieldChange}/>
<input name="phone" type="number" value={myFormFields.phone} onChange={onFormFieldChange}/>
</form>
)
}
export default MyForm;
Надеюсь это поможет!
Короче говоря, если вы используете компонент класса, вы должны инициализировать ввод с помощью состояния, например:
this.state = { the_name_attribute_of_the_input: "initial_value_or_empty_value" };
и вы должны сделать это для всех ваших входных данных, которые вы хотите изменить в коде.
В случае использования функциональных компонентов вы будете использовать хуки для управления входным значением, и вам нужно указать начальное значение для каждого ввода, которым вы хотите управлять позже, следующим образом:
const [name, setName] = React.useState({name: 'initialValue'});
Если вы не хотите иметь начальное значение, вы можете поместить пустую строку.
В моем случае компонент перерисовывал и бросал
A component is changing an uncontrolled input of type checkbox to be controlled
ошибка. Оказалось, что такое поведение было результатом невыполнения или наличия установленного флажка (иногда я получал). Вот как выглядел мой неисправный компонент:
import * as React from 'react';
import { WrappedFieldProps } from 'redux-form/lib/Field';
type Option = {
value: string;
label: string;
};
type CheckboxGroupProps = {
name: string;
options: Option[];
} & WrappedFieldProps;
const CheckboxGroup: React.FC<CheckboxGroupProps> = (props) => {
const {
name,
input,
options,
} = props;
const [value, setValue] = React.useState<string>();
const [checked, setChecked] = React.useState<{ [name: string]: boolean }>(
() => options.reduce((accu, option) => {
accu[option.value] = false;
return accu;
}, {}),
);
React.useEffect(() => {
input.onChange(value);
if (value) {
setChecked({
[value]: true, // that setChecked argument is wrong, causes error
});
} else {
setChecked(() => options.reduce((accu, option) => {
accu[option.value] = false;
return accu;
}, {}));
}
}, [value]);
return (
<>
{options.map(({ value, label }, index) => {
return (
<LabeledContainer
key={`${value}${index}`}
>
<Checkbox
name={`${name}[${index}]`}
checked={checked[value]}
value={value}
onChange={(event) => {
if (event.target.checked) {
setValue(value);
} else {
setValue(undefined);
}
return true;
}}
/>
{label}
</LabeledContainer>
);
})}
</>
);
};
Чтобы решить эту проблему, я изменил
useEffect
к этому
React.useEffect(() => {
input.onChange(value);
setChecked(() => options.reduce((accu, option) => {
accu[option.value] = option.value === value;
return accu;
}, {}));
}, [value]);
Это заставило все флажки сохранить свое состояние как
true
или же
false
не попадая в
undefined
который переключает управление с React на разработчика и наоборот.
Для тех, кто использует пользовательский интерфейс React Material и React-Hook-Form. В основном такие проблемы возникают, когда вы неправильно используете форму React Hook с компонентом React Material (используйте атрибут, который следует использовать для неконтролируемого элемента для контролируемого элемента, неправильно сбросьте настройки, сделайте неправильно установлен атрибут... и т. д.), например:
Автозаполнение:
- такая проблема может возникнуть, если вы не установили значение по умолчанию
null
значение атрибута в случае, если значение не указано, - или когда вы испортите использование
onChange
иvalue
вместе.
Один из возможных способов решения такой проблемы
value ={value||null}
или код ниже. Смотрите здесь для более подробной информацииonChange = {(event, values) => { onChange(values || null) }}
- такая проблема может возникнуть, если вы не установили значение по умолчанию
Флажок:
- с использованием
defaultChecked
для контролируемого флажка, хотя в документе четко указано, что этот атрибут следует использовать только тогда, когда компонент не контролируется, - другой возможный способ, который может привести к такой проблеме, — использование
checked={value}
безdefaultValue
это может привести к такой ошибке, более подробную информацию можно найти здесь.
React warning MUI: A component is changing the default checked state of an uncontrolled SwitchBase after being initialized
- с использованием
Для людей, использующих
Formik
, вам нужно добавить значение по умолчанию для конкретного поля
name
к форме
initialValues
.
Обычно это происходит только тогда, когда вы не контролируете значение поля, когда приложение запускается, и после того, как какое-то событие или какая-либо функция сработали или состояние изменилось, вы теперь пытаетесь контролировать значение в поле ввода.
Этот переход отсутствия контроля над входом и последующего контроля над ним и является причиной возникновения проблемы.
Лучший способ избежать этого - объявить некоторое значение для ввода в конструкторе компонента. Так что элемент input имеет значение с самого начала приложения.
Пожалуйста, попробуйте этот код
import React from "react";
class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = { name: "" };
this.onFieldChange = this.onFieldChange.bind(this);
}
onFieldChange(e) {
this.setState({[e.target.name]: e.target.value});
}
render() {
return (
<form className="add-support-staff-form">
<input name="name" type="text" value={this.state.name} onChange={this.onFieldChange} />
</form>
);
}
}
export default MyForm;
В моем случае произошла ошибка заклинания при установке состояния, из-за которого определенное значение было неопределенным.
Это мое состояние по умолчанию со значением
const [email, setEmail] = useState(true);
моя ошибка была-
setEmail(res?.data?.notificationSetting.is_email_notificatio);
а решение -
setEmail(res?.data?.notificationSetting.is_email_notification);
последний символ отсутствовал
У меня была такая же проблема с type='radio'
<input type='radio' checked={item.radio} ... />
причина заключалась в том, что item.radio не всегда имеет значение true или false , а, например, true или undefined . Убедитесь, что это всегда boolean , и проблема исчезнет.
<input type='radio' checked={!!item.radio} ... />
Для динамической установки свойств состояния для входных данных формы и сохранения их под контролем вы можете сделать что-то вроде этого:
const inputs = [
{ name: 'email', type: 'email', placeholder: "Enter your email"},
{ name: 'password', type: 'password', placeholder: "Enter your password"},
{ name: 'passwordConfirm', type: 'password', placeholder: "Confirm your password"},
]
class Form extends Component {
constructor(props){
super(props)
this.state = {} // Notice no explicit state is set in the constructor
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({
[name]: value
}
}
handleSubmit = (e) => {
// do something
}
render() {
<form onSubmit={(e) => handleSubmit(e)}>
{ inputs.length ?
inputs.map(input => {
const { name, placeholder, type } = input;
const value = this.state[name] || ''; // Does it exist? If so use it, if not use an empty string
return <input key={name} type={type} name={name} placeholder={placeholder} value={value} onChange={this.handleChange}/>
}) :
null
}
<button type="submit" onClick={(e) => e.preventDefault }>Submit</button>
</form>
}
}