React: modifica di un input non controllato


360

Ho un semplice componente di reazione con il modulo che credo abbia un input controllato:

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;

Quando eseguo la mia applicazione ricevo il seguente avviso:

Avvertenza: MyForm sta modificando un input non controllato di tipo testo da controllare. Gli elementi di input non devono passare da incontrollati a controllati (o viceversa). Decidi se utilizzare un elemento di input controllato o non controllato per la durata del componente

Credo che il mio input sia controllato poiché ha un valore. Mi chiedo cosa sto facendo di sbagliato?

Sto usando React 15.1.0

Risposte:


510

Credo che il mio input sia controllato poiché ha un valore.

Perché un input sia controllato, il suo valore deve corrispondere a quello di una variabile di stato.

Tale condizione inizialmente non è soddisfatta nel tuo esempio perché this.state.nameinizialmente non è impostata. Pertanto, l'input inizialmente non è controllato. Una volta che il onChangegestore viene attivato per la prima volta,this.state.name viene impostato. A quel punto, la condizione di cui sopra è soddisfatta e l'ingresso è considerato controllato. Questa transizione da incontrollata a controllata produce l'errore visto sopra.

Inizializzando this.state.name nel costruttore:

per esempio

this.state = { name: '' };

l'ingresso sarà controllato dall'inizio, risolvendo il problema. Vedi componenti controllati React per ulteriori esempi.

Non correlato a questo errore, è necessario disporre di una sola esportazione predefinita. Il codice sopra ha due.


7
È difficile leggere la risposta e seguire l'idea molte volte, ma questa risposta è il modo perfetto per raccontare storie e far capire allo spettatore allo stesso tempo. Rispondi a livello dio!
surajnew55,

2
Cosa succede se si hanno campi dinamici in un ciclo? ad es. hai impostato il nome del campo name={'question_groups.${questionItem.id}'}?
user3574492

2
Come funzionerebbero i campi dinamici. Sto generando il modulo in modo dinamico e quindi impostando i nomi dei campi in uno stato interno dell'oggetto, devo ancora prima impostare manualmente i nomi dei campi nello stato e poi trasferirli sul mio oggetto?
Giuseppe

13
Questa risposta è leggermente errata. Un input viene controllato se il valueprop ha un valore non nullo / non definito. L'elica non deve necessariamente corrispondere a una variabile di stato (potrebbe essere solo una costante e il componente sarebbe comunque considerato controllato). La risposta di Adam è più corretta e dovrebbe essere accettata.
ecraig12345,

2
Sì, questa è tecnicamente la risposta sbagliata (ma utile). Una nota al riguardo: se hai a che fare con i pulsanti di opzione (il cui "valore" è controllato in modo leggermente diverso), questo avviso di reazione può effettivamente essere lanciato anche se il tuo componente è controllato (attualmente). github.com/facebook/react/issues/6779 e può essere corretto aggiungendo un !! per la verità di isChecked
mheavers

123

Quando esegui il rendering del componente per la prima volta, this.state.namenon è impostato, quindi viene valutato undefinedo null, e finisci per passare value={undefined}o value={null}al tuo input.

Quando ReactDOM verifica se un campo è controllato, controlla sevalue != null (nota che non lo !=è !==) e, poiché undefined == nullin JavaScript, decide che non è controllato.

Quindi, quando onFieldChange()viene chiamato, this.state.nameè impostato su un valore stringa, l'input passa dall'essere incontrollato al controllo.

Se lo fai this.state = {name: ''}nel tuo costruttore, perché '' != null, il tuo input avrà un valore per tutto il tempo e quel messaggio sparirà.


5
Per quello che vale, può succedere la stessa cosa this.props.<whatever>, che è stato il problema da parte mia. Grazie Adam!
Don

Sì! Questo può accadere anche se passi una variabile calcolata in cui definisci render(), o un'espressione nel tag stesso, qualsiasi cosa a cui viene valutata undefined. Sono contento di poterti aiutare!
Leigh Brenecki il

1
Grazie per questa risposta: anche se non è accettata, spiega il problema molto meglio di "specificare un name".
machineghost,

1
Ho aggiornato la mia risposta per spiegare come funziona l'ingresso controllato. Vale la pena notare che ciò che conta qui non è che undefinedinizialmente venga passato un valore di . Piuttosto, è il fatto che this.state.namenon esiste come variabile di stato che rende incontrollato l'input. Ad esempio, se this.state = { name: undefined };avessi comportato il controllo dell'ingresso. Dovrebbe essere chiaro che ciò che conta è da dove viene il valore, non quale sia il valore.
fvgs

1
L'avere @fvgs this.state = { name: undefined }comporterebbe comunque un input non controllato. <input value={this.state.name} />desugars to React.createElement('input', {value: this.state.name}). Poiché l'accesso a una proprietà inesistente di un oggetto ritorna undefined, ciò restituisce esattamente la stessa React.createElement('input', {value: undefined})chiamata di funzione - se namenon è impostato o esplicitamente impostato su undefined, quindi React si comporta allo stesso modo. Puoi vedere questo comportamento in questo JSFiddle.
Leigh Brenecki,

52

Un altro approccio potrebbe essere l'impostazione del valore predefinito all'interno dell'input, in questo modo:

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

1
<input name = "name" type = "text" defaultValue = "" onChange = {this.onFieldChange ('name'). bind (this)} /> Penso che funzionerebbe anche
gandalf

Grazie mille, era esattamente quello che stavo cercando. Ci sono svantaggi o potenziali problemi nell'utilizzare questo schema che dovrei tenere a mente?
Marcel Otten,

Questo ha funzionato per me, poiché i campi sono dinamicamente resi dall'API, quindi non conosco il nome del campo quando il componente è montato. Questo ha funzionato a meraviglia!
Jamie - Fenrir Digital Ltd,

18

So che altri hanno già risposto. Ma un fattore molto importante qui che può aiutare altre persone con problemi simili:

Devi avere un onChangegestore aggiunto nel tuo campo di input (es. TextField, checkbox, radio, ecc.). Gestire sempre l'attività tramite il onChangegestore.

Esempio:

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

Quando si lavora con la casella di controllo , potrebbe essere necessario gestirne lo checkedstato con !!.

Esempio:

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

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


1
questo funziona per me, grazie, sì, sono d'accordo al 100% con quello, lo stato iniziale è {}, quindi il valore verificato sarà indefinito e lo renderà incontrollato,
Ping Woo,

13

La soluzione semplice per risolvere questo problema consiste nell'impostare un valore vuoto per impostazione predefinita:

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

8

Un potenziale svantaggio con l'impostazione del valore del campo su "" (stringa vuota) nel costruttore è se il campo è un campo opzionale e non viene modificato. A meno che non si esegua un massaggio prima di pubblicare il modulo, il campo verrà mantenuto nell'archivio dati come una stringa vuota anziché NULL.

Questa alternativa eviterà le stringhe vuote:

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

... 

<input name="name" type="text" value={this.state.name || ''}/>

6

Quando si utilizza onChange={this.onFieldChange('name').bind(this)}l'input, è necessario dichiarare la stringa vuota del proprio stato come valore del campo proprietà.

modo errato:

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

modo corretto:

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

6

Nel mio caso, mi mancava qualcosa di veramente banale.

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

Il mio stato era il seguente quando stavo ricevendo l'avvertimento:

state = {
   myObject: undefined
}

Alternando il mio stato per fare riferimento all'input del mio valore , il mio problema è stato risolto:

state = {
   myObject: {
      inputValue: ''
   }
}

2
Grazie per avermi aiutato a capire il vero problema che stavo avendo
rotimi-best

3

Se gli oggetti di scena sul componente sono stati passati come stato, inserisci un valore predefinito per i tag di input

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


3

Un aggiornamento per questo. Utilizzare per React Hooksconst [name, setName] = useState(" ")


Grazie 4 all'aggiornamento Giordania, questo errore è un po 'difficile da risolvere
Juan Salvador,

Perché " "e no ""? Questo ti fa perdere qualsiasi testo di suggerimento e se fai clic e digiti ottieni uno spazio subdolo prima di qualsiasi dato inserito.
Joshua Wade,

1

Ciò si verifica generalmente solo quando non si controlla il valore del file all'avvio dell'applicazione e dopo un evento o una funzione attivata o lo stato modificato, si sta ora cercando di controllare il valore nel campo di input.

Questa transizione di non avere il controllo sull'input e quindi avere il controllo su di esso è ciò che causa il problema in primo luogo.

Il modo migliore per evitarlo è dichiarare un valore per l'input nel costruttore del componente. In modo che l'elemento di input abbia valore dall'inizio dell'applicazione.


0

Per impostare dinamicamente le proprietà dello stato per gli input dei moduli e mantenerli controllati, si potrebbe fare qualcosa del genere:

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

È sufficiente creare un fallback su "" se this.state.name è null.

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

Questo funziona anche con le variabili useState.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.