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

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

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={.....} >

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

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

<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>    
  }
}
Другие вопросы по тегам