Реакция - изменение неконтролируемого входа

360

У меня есть простой компонент реагирования с формой, которая, как мне кажется, имеет один контролируемый вход:

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

alexs333
источник

Ответы:

510

Я считаю, что мой вклад контролируется, поскольку он имеет ценность.

Чтобы вход был управляемым, его значение должно соответствовать значению переменной состояния.

Это условие изначально не выполняется в вашем примере, потому что this.state.nameизначально не установлено. Поэтому вход изначально не контролируется. Как только onChangeобработчик запускается в первый раз, this.state.nameустанавливается. В этот момент вышеуказанное условие удовлетворяется, и вход считается контролируемым. Этот переход от неконтролируемого к контролируемому приводит к ошибке, замеченной выше.

Инициализируя this.state.nameв конструкторе:

например

this.state = { name: '' };

вход будет контролироваться с самого начала, устраняя проблему. См. React Controlled Components для большего количества примеров.

Независимо от этой ошибки, у вас должен быть только один экспорт по умолчанию. Ваш код выше имеет два.

fvgs
источник
7
Трудно прочитать ответ и многократно следовать этой идее, но этот ответ является идеальным способом рассказать историю и одновременно заставить зрителя понять. Ответь богу уровня!
surajnew55
2
Что делать, если у вас есть динамические поля в цикле? Например, вы установили имя поля name={'question_groups.${questionItem.id}'}?
user3574492
2
Как будут работать динамические поля. Генерирую ли форму динамически, а затем устанавливаю имена полей в объекте внутри состояния, нужно ли мне сначала сначала устанавливать имена полей в состоянии, а затем передавать их моему объекту?
Иосиф
13
Этот ответ немного неверен. Вход управляется, если valueреквизит имеет ненулевое / неопределенное значение. Реквизит не должен соответствовать переменной состояния (это может быть просто константа, и компонент все равно будет считаться контролируемым). Ответ Адама более правильный и должен быть принят.
ecraig12345
2
Да - это технически неправильный ответ (но полезный). Примечание об этом - если вы имеете дело с переключателями (чье «значение» управляется немного по-другому), это предупреждение о реакции может фактически выдаться, даже если ваш компонент находится под контролем (в настоящее время). github.com/facebook/react/issues/6779 , и может быть исправлено путем добавления !! к правдивости ischeck
mheavers
123

Когда вы впервые визуализируете свой компонент, this.state.nameон не установлен, поэтому он оценивается как undefinedили null, и в итоге вы переходите value={undefined}или value={null}к своему input.

Когда ReactDOM проверяет, контролируется ли поле, он проверяет, еслиvalue != null (обратите внимание, что это !=не так !==), и, поскольку undefined == nullв JavaScript, он решает, что оно неконтролируемое.

Таким образом, когда onFieldChange()вызывается, this.state.nameустанавливается строковое значение, ваш ввод переходит от неконтролируемого к контролю.

Если вы сделаете это this.state = {name: ''}в своем конструкторе, потому что '' != nullваш ввод будет иметь значение все время, и это сообщение исчезнет.

Ли Бренецки
источник
5
Для чего бы это ни стоило, может случиться то же самое this.props.<whatever>, что было проблемой с моей стороны. Спасибо, Адам!
Дон
Ага! Это также может произойти, если вы передадите вычисляемую переменную, в которой вы определили render(), или выражение в самом теге - все, что вычисляется undefined. Рад, что смог помочь!
Ли Бренецки
1
Спасибо за этот ответ: хотя он и не принят, он объясняет проблему гораздо лучше, чем просто "указать name".
machineghost
1
Я обновил свой ответ, чтобы объяснить, как работает контролируемый ввод. Стоит отметить, что здесь важно не то, что значение undefinedпередается изначально. Скорее, тот факт, что this.state.nameв качестве переменной состояния не существует, делает ввод неконтролируемым. Например, наличие this.state = { name: undefined };приведет к тому, что вход будет контролироваться. Следует понимать, что важно то, откуда взято значение, а не то, какое это значение.
fvgs
1
@fvgs this.state = { name: undefined }все равно приведет к неконтролируемому вводу. <input value={this.state.name} />Desugars для React.createElement('input', {value: this.state.name}). Поскольку доступ к несуществующему свойству объекта возвращает undefined, это приводит к точно такому же React.createElement('input', {value: undefined})вызову функции name- не установлено или явно установлено значение undefined, поэтому React ведет себя таким же образом. Вы можете увидеть это поведение в этом JSFiddle.
Ли Бренецки
52

Другим подходом может быть установка значения по умолчанию внутри вашего ввода, например:

 <input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>
JpCrow
источник
1
<input name = "name" type = "text" defaultValue = "" onChange = {this.onFieldChange ('name'). bind (this)} /> Я думаю, что это также будет работать
gandalf
Большое спасибо, это было именно то, что я искал. Есть ли какие-либо недостатки или потенциальные проблемы при использовании этого шаблона, которые я должен иметь в виду?
Марсель Оттен
Это сработало для меня, так как поля динамически визуализируются из API, поэтому я не знаю имени поля, когда компонент монтируется. Это сработало!
Джейми - Fenrir Digital Ltd
18

Я знаю, что другие уже ответили на это. Но очень важный фактор, который может помочь другим людям, испытывающим подобные проблемы:

Вы должны onChangeдобавить обработчик в поле ввода (например, textField, флажок, радио и т. Д.). Всегда обрабатывайте активность через onChangeобработчик.

Пример:

<input ... onChange={ this.myChangeHandler} ... />

Когда вы работаете с флажком, вам может потребоваться обработать его checkedсостояние с помощью !!.

Пример:

<input type="checkbox" checked={!!this.state.someValue} onChange={.....} >

Ссылка: https://github.com/facebook/react/issues/6779#issuecomment-326314716

Мухаммед Ханнан
источник
1
это работает для меня, спасибо, да, я на 100% согласен с этим, начальное состояние {}, поэтому проверенное значение будет неопределенным и сделает его неконтролируемым,
Ping Woo
13

Простое решение для решения этой проблемы - установить пустое значение по умолчанию:

<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />
Мустафа Глисси
источник
8

Один потенциальный недостаток при установке значения поля "" (пустая строка) в конструкторе - это если поле является необязательным и оставлено неотредактированным. Если вы не сделаете некоторый массаж перед отправкой формы, поле будет сохранено в вашем хранилище данных в виде пустой строки вместо NULL.

Эта альтернатива позволит избежать пустых строк:

constructor(props) {
    super(props);
    this.state = {
        name: null
    }
}

... 

<input name="name" type="text" value={this.state.name || ''}/>
Грег Р Тейлор
источник
6

При использовании onChange={this.onFieldChange('name').bind(this)}в качестве входных данных вы должны объявить пустую строку вашего состояния в качестве значения поля свойства.

неверный путь:

this.state ={
       fields: {},
       errors: {},
       disabled : false
    }

правильный путь:

this.state ={
       fields: {
         name:'',
         email: '',
         message: ''
       },
       errors: {},
       disabled : false
    }
KARTHIKEYAN.A
источник
6

В моем случае мне не хватало чего-то действительно тривиального.

<input value={state.myObject.inputValue} />

Мое состояние было следующим, когда я получал предупреждение:

state = {
   myObject: undefined
}

Чередуя мое состояние со ссылкой на ввод моей стоимости , моя проблема была решена:

state = {
   myObject: {
      inputValue: ''
   }
}
Менелаос Коцолларис
источник
2
Спасибо, что помогли мне понять реальную проблему, с которой я
столкнулся
3

Если реквизиты вашего компонента были переданы как состояние, установите значение по умолчанию для ваших входных тегов.

<input type="text" placeholder={object.property} value={object.property ? object.property : ""}>
Император Краузер
источник
3

Установите значение для свойства name в исходном состоянии.

this.state={ name:''};

lakmal_sathyajith
источник
3

Обновление для этого. Для использования React Hooksconst [name, setName] = useState(" ")

Джордан Маллен
источник
Спасибо 4 обновление Jordan, эту ошибку немного сложно решить
Хуан Сальвадор
Почему " "и нет ""? Это приводит к тому, что вы теряете любой текст подсказки, и если вы нажмете и введете текст, вы получите скрытый пробел перед вводом любых данных.
Джошуа Уэйд
1

Обычно это происходит только тогда, когда вы не контролируете значение поля, когда приложение запускается, и после того, как какое-то событие или какая-либо функция сработали или состояние изменилось, вы теперь пытаетесь контролировать значение в поле ввода.

Этот переход отсутствия контроля над входом и последующего контроля над ним и является причиной возникновения проблемы.

Лучший способ избежать этого - объявить некоторое значение для ввода в конструкторе компонента. Так что элемент input имеет значение с самого начала приложения.

Ашиш Сингх
источник
0

Для динамической установки свойств состояния для входов формы и их контроля вы можете сделать что-то вроде этого:

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>    
  }
}
отметка
источник
0

Просто создайте запасной вариант для '', если this.state.name имеет значение null.

<input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>

Это также работает с переменными useState.

RobKohr
источник