Reaccionar: cambiar una entrada no controlada

360

Tengo un componente de reacción simple con la forma que creo que tiene una entrada controlada:

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;

Cuando ejecuto mi aplicación, aparece la siguiente advertencia:

Advertencia: MyForm está cambiando una entrada no controlada de texto de tipo para controlar. Los elementos de entrada no deben cambiar de no controlado a controlado (o viceversa). Decida entre usar un elemento de entrada controlado o no controlado durante la vida útil del componente

Creo que mi entrada está controlada ya que tiene un valor. Me pregunto qué estoy haciendo mal.

Estoy usando React 15.1.0

alexs333
fuente

Respuestas:

510

Creo que mi entrada está controlada ya que tiene un valor.

Para que una entrada sea controlada, su valor debe corresponder al de una variable de estado.

Esa condición no se cumple inicialmente en su ejemplo porque this.state.nameno se establece inicialmente. Por lo tanto, la entrada inicialmente no está controlada. Una vez que onChangese activa el controlador por primera vez, this.state.namese establece. En ese punto, se cumple la condición anterior y se considera que la entrada está controlada. Esta transición de no controlado a controlado produce el error visto anteriormente.

Inicializando this.state.nameen el constructor:

p.ej

this.state = { name: '' };

la entrada se controlará desde el principio, solucionando el problema. Ver Reaccionar componentes controlados para más ejemplos.

Sin relación con este error, solo debe tener una exportación predeterminada. Su código anterior tiene dos.

fvgs
fuente
77
Es difícil leer la respuesta y seguir la idea muchas veces, pero esta respuesta es la manera perfecta de contar historias y hacer que el espectador entienda al mismo tiempo. Nivel de respuesta dios!
surajnew55
2
¿Qué pasa si tiene campos dinámicos en un bucle? por ejemplo, ¿configura el nombre del campo como name={'question_groups.${questionItem.id}'}?
user3574492
2
Cómo funcionarían los campos dinámicos. Estoy generando el formulario dinámicamente y luego establezco los nombres de campo en un objeto dentro del estado, ¿todavía tengo que configurar primero manualmente los nombres de campo en estado y luego transferirlos a mi objeto?
Joseph
13
Esta respuesta es ligeramente incorrecta. Se controla una entrada si el valueaccesorio tiene un valor no nulo / indefinido. El accesorio no necesita corresponder a una variable de estado (podría ser una constante y el componente aún se consideraría controlado). La respuesta de Adán es más correcta y debe ser aceptada.
ecraig12345
2
Sí, técnicamente esta es la respuesta incorrecta (pero útil). Una nota sobre esto: si se trata de botones de radio (cuyo 'valor' se controla de manera un poco diferente), esta advertencia de reacción puede arrojarse incluso si su componente está controlado (actualmente). github.com/facebook/react/issues/6779 , y se puede solucionar agregando un !! a la truthiness de isChecked
mheavers
123

Cuando renderiza su componente por primera vez, this.state.nameno está configurado, por lo que se evalúa en undefinedo null, y termina pasando value={undefined}o value={null}a su input.

Cuando los cheques ReactDOM a ver si un campo se controla, comprueba para ver sivalue != null (nota que es !=, no !==), y puesto que undefined == nullen JavaScript, se decide que no se la controla.

Entonces, cuando onFieldChange()se llama, this.state.namese establece en un valor de cadena, su entrada pasa de no ser controlada a ser controlada.

Si lo hace this.state = {name: ''}en su constructor, porque '' != nullsu entrada tendrá un valor todo el tiempo, y ese mensaje desaparecerá.

Leigh Brenecki
fuente
55
Por lo que vale, puede suceder lo mismo this.props.<whatever>, que fue el problema de mi parte. Gracias Adam!
Don
¡Sí! Esto también podría suceder si pasa una variable calculada en la que define render()o una expresión en la propia etiqueta, cualquier cosa que se evalúe undefined. Me alegro de poder ayudar!
Leigh Brenecki
1
Gracias por esta respuesta: aunque no es la aceptada, explica el problema mucho mejor que simplemente "especificar un name".
machineghost
1
He actualizado mi respuesta para explicar cómo funciona la entrada controlada. Vale la pena señalar que lo que importa aquí no es que undefinedinicialmente se pase un valor de . Más bien, es el hecho de que this.state.nameno existe como una variable de estado lo que hace que la entrada no esté controlada. Por ejemplo, tener this.state = { name: undefined };resultaría en el control de la entrada. Debe entenderse que lo que importa es de dónde proviene el valor, no cuál es el valor.
fvgs
1
@fvgs tener this.state = { name: undefined }todavía resultaría en una entrada no controlada. <input value={this.state.name} />desugars a React.createElement('input', {value: this.state.name}). Debido a que el acceso a una propiedad inexistente de un objeto devuelve undefined, esto se evalúa exactamente a la misma React.createElement('input', {value: undefined})llamada a la función, ya namesea ​​que no esté establecido o establecido explícitamente undefined, entonces React se comporta de la misma manera. Puede ver este comportamiento en este JSFiddle.
Leigh Brenecki
52

Otro enfoque podría ser establecer el valor predeterminado dentro de su entrada, como este:

 <input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>
JpCrow
fuente
1
<input name = "name" type = "text" defaultValue = "" onChange = {this.onFieldChange ('name'). bind (this)} /> Creo que esto también funcionaría
gandalf
Muchas gracias, esto era exactamente lo que estaba buscando. ¿Hay alguna desventaja o problema potencial al usar este patrón que debo tener en cuenta?
Marcel Otten
Esto funcionó para mí, ya que los campos se representan dinámicamente desde la API, por lo que no sé el nombre del campo cuando se monta el componente. Esto funcionó de maravilla!
Jamie - Fenrir Digital Ltd
18

Sé que otros ya han respondido esto. Pero un factor muy importante aquí que puede ayudar a otras personas que experimentan un problema similar:

Debe tener un onChangecontrolador agregado en su campo de entrada (por ejemplo, textField, casilla de verificación, radio, etc.). Maneje siempre la actividad a través del onChangecontrolador.

Ejemplo:

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

Cuando trabaje con la casilla de verificación, es posible que deba controlar su checkedestado !!.

Ejemplo:

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

Referencia: https://github.com/facebook/react/issues/6779#issuecomment-326314716

Muhammad Hannan
fuente
1
esto funciona para mí, gracias, sí, estoy 100% de acuerdo con eso, el estado inicial es {}, por lo que el valor verificado no estará definido y lo hará incontrolado,
Ping Woo
13

La solución simple para resolver este problema es establecer un valor vacío por defecto:

<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />
MUSTAPHA GHLISSI
fuente
8

Una posible desventaja de establecer el valor del campo en "" (cadena vacía) en el constructor es si el campo es un campo opcional y se deja sin editar. A menos que haga un poco de masaje antes de publicar su formulario, el campo se mantendrá en su almacenamiento de datos como una cadena vacía en lugar de NULL.

Esta alternativa evitará cadenas vacías:

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

... 

<input name="name" type="text" value={this.state.name || ''}/>
Greg R Taylor
fuente
6

Cuando use onChange={this.onFieldChange('name').bind(this)}en su entrada, debe declarar su cadena vacía de estado como un valor del campo de propiedad.

forma incorrecta:

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

forma correcta:

this.state ={
       fields: {
         name:'',
         email: '',
         message: ''
       },
       errors: {},
       disabled : false
    }
KARTHIKEYAN.A
fuente
6

En mi caso, me faltaba algo realmente trivial.

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

Mi estado era el siguiente cuando recibí la advertencia:

state = {
   myObject: undefined
}

Al alternar mi estado para hacer referencia a la entrada de mi valor , mi problema se resolvió:

state = {
   myObject: {
      inputValue: ''
   }
}
Menelaos Kotsollaris
fuente
2
Gracias por ayudarme a comprender el verdadero problema que estaba teniendo
rotimi-best
3

Si los accesorios de su componente se pasaron como un estado, coloque un valor predeterminado para sus etiquetas de entrada

<input type="text" placeholder={object.property} value={object.property ? object.property : ""}>
Emperador Krauser
fuente
3

Establezca un valor para la propiedad 'nombre' en el estado inicial.

this.state={ name:''};

lakmal_sathyajith
fuente
3

Una actualización para esto. Para usar React Hooksconst [name, setName] = useState(" ")

Jordan Mullen
fuente
Gracias 4 la actualización Jordan, este error es un poco difícil de resolver
Juan Salvador
¿Por qué " "y no ""? Esto hace que pierda cualquier texto de sugerencia, y si hace clic y escribe, obtendrá un espacio furtivo antes de cualquier dato que ingrese.
Joshua Wade
1

Esto generalmente ocurre solo cuando no está controlando el valor del archivo cuando se inició la aplicación y después de que algún evento o alguna función se activó o el estado cambió, ahora está tratando de controlar el valor en el campo de entrada.

Esta transición de no tener control sobre la entrada y luego tener control sobre ella es lo que hace que el problema ocurra en primer lugar.

La mejor manera de evitar esto es declarando algún valor para la entrada en el constructor del componente. Para que el elemento de entrada tenga valor desde el inicio de la aplicación.

Ashish Singh
fuente
0

Para establecer dinámicamente propiedades de estado para entradas de formulario y mantenerlas controladas, puede hacer algo como esto:

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>    
  }
}
marca
fuente
0

Simplemente cree un respaldo a '' si this.state.name es nulo.

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

Esto también funciona con las variables useState.

RobKohr
fuente