React - modification d'une entrée non contrôlée


360

J'ai un composant react simple avec la forme qui je pense avoir une entrée contrôlée:

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;

Lorsque j'exécute mon application, j'obtiens l'avertissement suivant:

Avertissement: MyForm modifie une entrée non contrôlée de type texte à contrôler. Les éléments d'entrée ne doivent pas passer de non contrôlé à contrôlé (ou vice versa). Décider entre l'utilisation d'un élément d'entrée contrôlé ou non contrôlé pendant la durée de vie du composant

Je crois que mon entrée est contrôlée car elle a une valeur. Je me demande ce que je fais mal?

J'utilise React 15.1.0

Réponses:


510

Je crois que mon entrée est contrôlée car elle a une valeur.

Pour qu'une entrée soit contrôlée, sa valeur doit correspondre à celle d'une variable d'état.

Cette condition n'est pas initialement remplie dans votre exemple car elle this.state.namen'est pas initialement définie. Par conséquent, l'entrée est initialement non contrôlée. Une fois que le onChangegestionnaire est déclenché pour la première fois, il this.state.nameest défini. À ce stade, la condition ci-dessus est satisfaite et l'entrée est considérée comme contrôlée. Cette transition de non contrôlé à contrôlé produit l'erreur vue ci-dessus.

En initialisant this.state.namedans le constructeur:

par exemple

this.state = { name: '' };

l'entrée sera contrôlée dès le début, corrigeant le problème. Voir React Controlled Components pour plus d'exemples.

Sans rapport avec cette erreur, vous ne devriez avoir qu'une seule exportation par défaut. Votre code ci-dessus en a deux.


7
Il est difficile de lire la réponse et de suivre l'idée plusieurs fois, mais cette réponse est le moyen idéal de raconter des histoires et de faire comprendre au spectateur en même temps. Réponse niveau dieu!
surajnew55

2
Et si vous avez des champs dynamiques dans une boucle? Par exemple, vous définissez le nom du champ sur name={'question_groups.${questionItem.id}'}?
user3574492

2
Comment les champs dynamiques fonctionneraient-ils? Suis en train de générer le formulaire de manière dynamique, puis définissez les noms de champ dans un objet à l'intérieur de l'état, dois-je encore d'abord définir manuellement les noms de champ dans l'état, puis les transférer vers mon objet?
Joseph

13
Cette réponse est légèrement incorrecte. Une entrée est contrôlée si l' valuehélice a une valeur non nulle / non définie. Le prop n'a pas besoin de correspondre à une variable d'état (il pourrait simplement s'agir d'une constante et le composant serait toujours considéré comme contrôlé). La réponse d'Adam est plus correcte et devrait être acceptée.
ecraig12345

2
Ouais - c'est techniquement la mauvaise réponse (mais utile). Une note à ce sujet - si vous avez affaire à des boutons radio (dont la «valeur» est contrôlée un peu différemment), cet avertissement de réaction peut réellement être lancé même si votre composant est contrôlé (actuellement). github.com/facebook/react/issues/6779 , et peut être corrigé en ajoutant un !! à la véracité d'isChecked
mheavers

123

Lorsque vous effectuez le rendu de votre composant pour la première fois, il this.state.namen'est pas défini, il est donc évalué à undefinedou null, et vous finissez par passer à value={undefined}ou value={null}à votre input.

Lorsque ReactDOM vérifie si un champ est contrôlé, il vérifie sivalue != null (notez que ce n'est !=pas le cas !==), et puisque undefined == nulldans JavaScript, il décide qu'il n'est pas contrôlé.

Ainsi, lorsque onFieldChange()est appelé, this.state.nameest défini sur une valeur de chaîne, votre entrée passe de non contrôlée à contrôlée.

Si vous le faites this.state = {name: ''}dans votre constructeur, car '' != nullvotre entrée aura une valeur tout le temps et ce message disparaîtra.


5
Pour ce que ça vaut, la même chose peut arriver this.props.<whatever>, ce qui était le problème de mon côté. Merci, Adam!
Don

Oui! Cela peut également se produire si vous transmettez une variable calculée que vous définissez dans render(), ou une expression dans la balise elle-même - tout ce qui est évalué undefined. Heureux d'avoir pu aider!
Leigh Brenecki

1
Merci pour cette réponse: même si ce n'est pas celle acceptée, elle explique le problème bien mieux que de simplement "spécifier un name".
machineghost

1
J'ai mis à jour ma réponse pour expliquer comment fonctionne l'entrée contrôlée. Il convient de noter que ce qui importe ici n'est pas qu'une valeur de undefinedsoit transmise initialement. C'est plutôt le fait qu'il this.state.namen'existe pas en tant que variable d'état qui rend l'entrée non contrôlée. Par exemple, avoir this.state = { name: undefined };entraînerait le contrôle de l'entrée. Il faut comprendre que ce qui compte, c'est d'où vient la valeur, et non quelle est la valeur.
fvgs

1
Le fait d'avoir @fvgs this.state = { name: undefined }entraînerait toujours une entrée non contrôlée. <input value={this.state.name} />desugars à React.createElement('input', {value: this.state.name}). Étant donné que l'accès à une propriété inexistante d'un objet renvoie undefined, cela correspond exactement au même appel de fonction: React.createElement('input', {value: undefined})s'il namen'est pas défini ou explicitement défini undefined, React se comporte de la même manière. Vous pouvez voir ce comportement dans ce JSFiddle.
Leigh Brenecki

52

Une autre approche pourrait être de définir la valeur par défaut dans votre entrée, comme ceci:

 <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)} /> Je pense que cela fonctionnerait également
gandalf

Merci beaucoup, c'était exactement ce que je cherchais. Y a-t-il des inconvénients ou des problèmes potentiels à utiliser ce modèle que je dois garder à l'esprit?
Marcel Otten

Cela a fonctionné pour moi, car les champs sont rendus dynamiquement à partir de l'API, donc je ne connais pas le nom du champ lorsque le composant est monté. Cela a fonctionné un régal!
Jamie - Fenrir Digital Ltd

18

Je sais que d'autres y ont déjà répondu. Mais un facteur très important ici peut aider d'autres personnes confrontées à un problème similaire:

Vous devez avoir un onChangegestionnaire ajouté dans votre champ de saisie (par exemple textField, case à cocher, radio, etc.). Gérez toujours l'activité via le onChangegestionnaire.

Exemple:

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

Lorsque vous travaillez avec la case à cocher, vous devrez peut-être gérer son checkedétat avec !!.

Exemple:

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

Référence: https://github.com/facebook/react/issues/6779#issuecomment-326314716


1
cela fonctionne pour moi, merci, oui, je suis à 100% d'accord avec cela, l'état initial est {}, donc la valeur vérifiée sera indéfinie et la rendra non contrôlée,
Ping Woo

13

Une solution simple pour résoudre ce problème consiste à définir une valeur vide par défaut:

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

8

Un inconvénient potentiel avec la définition de la valeur du champ sur "" (chaîne vide) dans le constructeur est que le champ est un champ facultatif et n'est pas modifié. Sauf si vous effectuez un massage avant de publier votre formulaire, le champ sera conservé dans votre stockage de données sous la forme d'une chaîne vide au lieu de NULL.

Cette alternative évitera les chaînes vides:

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

... 

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

6

Lorsque vous utilisez onChange={this.onFieldChange('name').bind(this)}dans votre entrée, vous devez déclarer votre chaîne vide d'état comme valeur du champ de propriété.

manière incorrecte:

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

manière correcte:

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

6

Dans mon cas, il me manquait quelque chose de vraiment trivial.

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

Mon état était le suivant lorsque j'ai reçu l'avertissement:

state = {
   myObject: undefined
}

En alternant mon état pour référencer l'entrée de ma valeur , mon problème a été résolu:

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

2
Merci de m'avoir aidé à comprendre le vrai problème que j'avais
rotimi-best

3

Si les accessoires de votre composant ont été passés en tant qu'état, mettez une valeur par défaut pour vos balises d'entrée

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


3

Une mise à jour pour cela. Pour l'utilisation de React Hooksconst [name, setName] = useState(" ")


Merci 4 la mise à jour Jordan, cette erreur est un peu difficile à résoudre
Juan Salvador

Pourquoi " "et non ""? Cela vous fait perdre tout texte d'indication, et si vous cliquez et tapez, vous obtenez un espace sournois avant toutes les données que vous entrez.
Joshua Wade

1

Cela ne se produit généralement que lorsque vous ne contrôlez pas la valeur du fichier lorsque l'application a démarré et après un événement ou une fonction déclenchée ou l'état a changé, vous essayez maintenant de contrôler la valeur dans le champ de saisie.

Cette transition de ne pas avoir le contrôle sur l'entrée, puis de le contrôler, est ce qui provoque le problème en premier lieu.

La meilleure façon d'éviter cela est de déclarer une valeur pour l'entrée dans le constructeur du composant. Pour que l'élément d'entrée ait une valeur dès le début de l'application.


0

Pour définir dynamiquement les propriétés d'état des entrées de formulaire et les garder contrôlées, vous pouvez faire quelque chose comme ceci:

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

Créez simplement un repli sur '' si le this.state.name est null.

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

Cela fonctionne également avec les variables useState.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.