web-dev-qa-db-fra.com

Comment accéder à l'état de l'enfant dans React?

J'ai la structure suivante:

FormEditor - en retient plusieurs FieldEditor FieldEditor - édite un champ du formulaire et enregistre diverses valeurs à ce sujet dans son état

Lorsqu'un bouton est cliqué dans FormEditor, je souhaite pouvoir collecter des informations sur les champs de tous les composants FieldEditor, des informations qui se trouvent dans leur état et les avoir toutes dans FormEditor.

J'ai envisagé de stocker les informations sur les champs en dehors de l'état de FieldEditor et de les mettre dans l'état de FormEditor. Cependant, cela nécessiterait que FormEditor écoute chacun de ses composants FieldEditor à mesure qu'ils changent et stockent leurs informations dans son état.

Est-ce que je ne peux pas simplement accéder à l'état des enfants à la place? Est-ce idéal?

185
Moshe Revah

Si vous avez déjà un gestionnaire onChange pour les différents FieldEditors, je ne vois pas pourquoi vous ne pouviez pas simplement déplacer l'état vers le composant FormEditor et simplement transmettre un rappel à FieldEditors qui mettrait à jour l'état parent. Cela me semble être un moyen plus réactif de le faire.

Quelque chose le long de cette ligne peut-être:

class FieldEditor extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    const text = event.target.value;
    this.props.onChange(this.props.id, text);
  }

  render() {
    return (
      <div className="field-editor">
        <input onChange={this.handleChange} value={this.props.value} />
      </div>
    );
  }
}

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};

    this.handleFieldChange = this.handleFieldChange.bind(this);
  }

  handleFieldChange(fieldId, value) {
    this.setState({ [fieldId]: value });
  }

  render() {
    const fields = this.props.fields.map(field => (
      <FieldEditor
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        {fields}
        <div>{JSON.stringify(this.state)}</div>
      </div>
    );
  }
}

// Convert to class component and add ability to dynamically add/remove fields by having it in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};

ReactDOM.render(<App />, document.body);

http://jsbin.com/qeyoxobixa/edit?js,output

Edit: juste pour le plaisir, j'ai réécrit l'exemple ci-dessus en utilisant des crochets pour ceux qui sont intéressés:

const FieldEditor = ({ value, onChange, id }) => {
  const handleChange = event => {
    const text = event.target.value;
    onChange(id, text);
  };

  return (
    <div className="field-editor">
      <input onChange={handleChange} value={value} />
    </div>
  );
};

const FormEditor = props => {
  const [values, setValues] = useState({});
  const handleFieldChange = (fieldId, value) => {
    setValues({ ...values, [fieldId]: value });
  };
  const fields = props.fields.map(field => (
    <FieldEditor
      key={field}
      id={field}
      onChange={handleFieldChange}
      value={values[field]}
    />
  ));

  return (
    <div>
      {fields}
      <pre>{JSON.stringify(values, null, 2)}</pre>
    </div>
  );
};

// To add abillity to dynamically add/remove fields keep the list in state
const App = () => {
  const fields = ["field1", "field2", "anotherField"];

  return <FormEditor fields={fields} />;
};
117
Markus-ipse

Avant d’entrer dans les détails sur la façon d’accéder à l’état d’un composant enfant, assurez-vous de lire la réponse de Markus-ipse concernant une meilleure solution pour gérer ce scénario particulier.

Si vous souhaitez effectivement accéder à l'état des enfants d'un composant, vous pouvez affecter une propriété appelée ref à chaque enfant. Il existe maintenant deux manières d'implémenter des références: Utilisation de React.createRef() et des références de rappel.

Utiliser React.createRef()

C'est actuellement la méthode recommandée pour utiliser les références à partir de React 16.3 (Voir la documentation pour plus d'informations). Si vous utilisez une version antérieure, reportez-vous ci-dessous aux références de rappel.

Vous devrez créer une nouvelle référence dans le constructeur de votre composant parent, puis l'affecter à un enfant via l'attribut ref.

class FormEditor extends React.Component {
  constructor(props) {
    super(props);
    this.FieldEditor1 = React.createRef();
  }
  render() {
    return <FieldEditor ref={this.FieldEditor1} />;
  }
}

Pour accéder à ce type de référence, vous devez utiliser:

const currentFieldEditor1 = this.FieldEditor1.current;

Cela retournera une instance du composant monté afin que vous puissiez ensuite utiliser currentFieldEditor1.state pour accéder à l'état.

Juste une petite remarque pour dire que si vous utilisez ces références sur un nœud DOM au lieu d’un composant (par exemple <div ref={this.divRef} />), alors this.divRef.current renverra l’élément DOM sous-jacent au lieu d’une instance de composant.

Références de rappel

Cette propriété prend une fonction de rappel à laquelle est transmise une référence au composant attaché. Ce rappel est exécuté immédiatement après le montage ou le démontage du composant.

Par exemple:

<FieldEditor
    ref={(fieldEditor1) => {this.fieldEditor1 = fieldEditor1;}
    {...props}
/>

Dans ces exemples, la référence est stockée sur le composant parent. Pour appeler ce composant dans votre code, vous pouvez utiliser:

this.fieldEditor1

puis utilisez this.fieldEditor1.state pour obtenir l'état.

Une chose à noter, assurez-vous que votre composant enfant a été rendu avant d'essayer d'y accéder ^ _ ^

Comme ci-dessus, si vous utilisez ces références sur un nœud DOM au lieu d'un composant (par exemple <div ref={(divRef) => {this.myDiv = divRef;}} />), alors this.divRef renverra l'élément DOM sous-jacent au lieu d'une instance de composant.

Informations complémentaires

Si vous voulez en savoir plus sur la propriété de référence de React, consultez ceci page sur Facebook.

Assurez-vous de lire la section " Ne pas abuser des références " qui dit que vous ne devez pas utiliser la state de l'enfant pour "faire bouger les choses".

J'espère que cela aide ^ _ ^

Edit: Ajout de la méthode React.createRef() pour la création de références. Suppression du code ES5.

167
Martoid Prime

Vous pouvez maintenant accéder à l'état de InputField, qui est l'enfant de FormEditor.

Fondamentalement, chaque fois que l'état du champ de saisie (enfant) change, nous obtenons la valeur de l'objet événement, puis nous transmettons cette valeur au parent dans lequel l'état dans le parent est défini.

En cliquant sur le bouton, nous imprimons simplement l’état des champs de saisie.

Le point clé ici est que nous utilisons les accessoires pour obtenir l'ID/la valeur du champ de saisie et pour appeler les fonctions définies comme attributs du champ de saisie pendant que nous générons les champs de saisie enfants réutilisables.

class InputField extends React.Component{
  handleChange = (event)=> {
    const val = event.target.value;
    this.props.onChange(this.props.id , val);
  }

  render() {
    return(
      <div>
        <input type="text" onChange={this.handleChange} value={this.props.value}/>
        <br/><br/>
      </div>
    );
  }
}       


class FormEditorParent extends React.Component {
  state = {};
  handleFieldChange = (inputFieldId , inputFieldValue) => {
    this.setState({[inputFieldId]:inputFieldValue});
  }
  //on Button click simply get the state of the input field
  handleClick = ()=>{
    console.log(JSON.stringify(this.state));
  }

  render() {
    const fields = this.props.fields.map(field => (
      <InputField
        key={field}
        id={field}
        onChange={this.handleFieldChange}
        value={this.state[field]}
      />
    ));

    return (
      <div>
        <div>
          <button onClick={this.handleClick}>Click Me</button>
        </div>
        <div>
          {fields}
        </div>
      </div>
    );
  }
}

const App = () => {
  const fields = ["field1", "field2", "anotherField"];
  return <FormEditorParent fields={fields} />;
};

ReactDOM.render(<App/>, mountNode);
2
Dhana