ReactJS: setState sur le parent dans le composant enfant

89

Quel est le modèle recommandé pour effectuer un setState sur un parent à partir d'un composant enfant.

var Todos = React.createClass({
  getInitialState: function() {
    return {
      todos: [
        "I am done",
        "I am not done"
      ]
    }
  },

  render: function() {
    var todos = this.state.todos.map(function(todo) {
      return <div>{todo}</div>;
    });

    return <div>
      <h3>Todo(s)</h3>
      {todos}
      <TodoForm />
    </div>;
  }
});

var TodoForm = React.createClass({
  getInitialState: function() {
    return {
      todoInput: ""
    }
  },

  handleOnChange: function(e) {
    e.preventDefault();
    this.setState({todoInput: e.target.value});
  },

  handleClick: function(e) {
    e.preventDefault();
    //add the new todo item
  },

  render: function() {
    return <div>
      <br />
      <input type="text" value={this.state.todoInput} onChange={this.handleOnChange} />
      <button onClick={this.handleClick}>Add Todo</button>
    </div>;
  }
});

React.render(<Todos />, document.body)

J'ai un tableau d'articles à faire qui sont conservés dans l'état du parent. Je veux accéder à l'état du parent et ajouter un nouvel élément todo, à partir du composant TodoForms handleClick. Mon idée est de faire un setState sur le parent, ce qui rendra l'élément todo nouvellement ajouté.

Pavithra
la source
Je vais juste envoyer du
jujiyangasli
Je reçois une erreursetState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the MyModal component.
Matt
J'obtiens la même erreur que je ne peux pas définirState sur un composant non monté. Y avait-il une solution de contournement pour cela?
Kevin Burton le

Réponses:

81

Dans votre parent, vous pouvez créer une fonction comme celle addTodoItemqui effectuera le setState requis, puis passera cette fonction comme accessoire au composant enfant.

var Todos = React.createClass({

  ...

  addTodoItem: function(todoItem) {
    this.setState(({ todos }) => ({ todos: { ...todos, todoItem } }));
  },

  render: function() {

    ...

    return <div>
      <h3>Todo(s)</h3>
      {todos}
      <TodoForm addTodoItem={this.addTodoItem} />
    </div>
  }
});

var TodoForm = React.createClass({
  handleClick: function(e) {
    e.preventDefault();
    this.props.addTodoItem(this.state.todoInput);
    this.setState({todoInput: ""});
  },

  ...

});

Vous pouvez appeler addTodoItemdans le handleClick de TodoForm. Cela fera un setState sur le parent qui rendra l'élément todo nouvellement ajouté. J'espère que vous avez l'idée.

Violon ici.

Deepak
la source
6
Que fait l' <<opérateur this.state.todos << todoItem;ici?
Gabriel Garrett
@zavtra Little Ruby confusion en cours, je suppose
azium
7
C'est une mauvaise pratique de muter this.statedirectement. Mieux vaut utiliser setState fonctionnel. reactjs.org/docs/react-component.html#setstate
Rohmer
2
le violon est cassé
Hunter Nelson
1
Comment cette solution (mise à jour) serait-elle implémentée à l'aide des hooks React?
ecoe
11

Ce sont tous essentiellement corrects, je pensais juste que je voudrais signaler la nouvelle documentation officielle (ish) de réaction qui recommande essentiellement: -

Il devrait y avoir une seule «source de vérité» pour toutes les données qui changent dans une application React. En général, l'état est d'abord ajouté au composant qui en a besoin pour le rendu. Ensuite, si d'autres composants en ont également besoin, vous pouvez le soulever jusqu'à leur ancêtre commun le plus proche. Au lieu d'essayer de synchroniser l'état entre différents composants, vous devez vous fier au flux de données descendant.

Voir https://reactjs.org/docs/lifting-state-up.html . La page fonctionne également à travers un exemple.

TattyDeMelbourne
la source
8

Vous pouvez créer une fonction addTodo dans le composant parent, la lier à ce contexte, la transmettre au composant enfant et l'appeler à partir de là.

// in Todos
addTodo: function(newTodo) {
    // add todo
}

Ensuite, dans Todos.render, vous feriez

<TodoForm addToDo={this.addTodo.bind(this)} />

Appelez ceci dans TodoForm avec

this.props.addToDo(newTodo);
rallye
la source
C'était tellement utile. Sans faire bind(this)au moment de passer la fonction, il ne lancait pas d'erreur de telle fonction this.setState is not a function.
pratpor
6

Pour ceux qui maintiennent l'état avec le React Hook useState, j'ai adapté les suggestions ci-dessus pour créer une application de curseur de démonstration ci-dessous. Dans l'application de démonstration, le composant de curseur enfant conserve l'état du parent.

La démo utilise également useEffecthook. (et moins important, useRefcrochet)

import React, { useState, useEffect, useCallback, useRef } from "react";

//the parent react component
function Parent() {

  // the parentState will be set by its child slider component
  const [parentState, setParentState] = useState(0);

  // make wrapper function to give child
  const wrapperSetParentState = useCallback(val => {
    setParentState(val);
  }, [setParentState]);

  return (
    <div style={{ margin: 30 }}>
      <Child
        parentState={parentState}
        parentStateSetter={wrapperSetParentState}
      />
      <div>Parent State: {parentState}</div>
    </div>
  );
};

//the child react component
function Child({parentStateSetter}) {
  const childRef = useRef();
  const [childState, setChildState] = useState(0);

  useEffect(() => {
    parentStateSetter(childState);
  }, [parentStateSetter, childState]);

  const onSliderChangeHandler = e => {
  //pass slider's event value to child's state
    setChildState(e.target.value);
  };

  return (
    <div>
      <input
        type="range"
        min="1"
        max="255"
        value={childState}
        ref={childRef}
        onChange={onSliderChangeHandler}
      ></input>
    </div>
  );
};

export default Parent;
NicoWheat
la source
Vous pouvez utiliser cette application avec create-react-app et remplacer tout le code dans App.js par le code ci-dessus.
NicoWheat le
Salut, je suis nouveau pour réagir et je me demandais: est-il nécessaire de l'utiliser useEffect? Pourquoi avons-nous besoin de stocker les données à la fois dans l'état parent et enfant?
538ROMEO
1
Les exemples ne visent pas à montrer pourquoi nous devons stocker des données à la fois dans le parent et dans l'enfant - la plupart du temps, vous n'en avez pas besoin. Mais, si vous vous trouvez dans une situation où l'enfant doit définir l'état parent, voici comment vous pouvez le faire. useEffect est nécessaire si vous souhaitez définir l'état parent COMME EFFET du changement de childState.
NicoWheat le
3
parentSetState={(obj) => { this.setState(obj) }}
Dezman
la source
4
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire sur la manière et / ou la raison pour laquelle il résout le problème améliorerait la valeur à long terme de la réponse.
Nic3500
2

J'ai trouvé la solution simple et fonctionnelle suivante pour passer des arguments d'un composant enfant au composant parent:

//ChildExt component
class ChildExt extends React.Component {
    render() {
        var handleForUpdate =   this.props.handleForUpdate;
        return (<div><button onClick={() => handleForUpdate('someNewVar')}>Push me</button></div>
        )
    }
}

//Parent component
class ParentExt extends React.Component {   
    constructor(props) {
        super(props);
        var handleForUpdate = this.handleForUpdate.bind(this);
    }
    handleForUpdate(someArg){
            alert('We pass argument from Child to Parent: \n' + someArg);   
    }

    render() {
        var handleForUpdate =   this.handleForUpdate;    
        return (<div>
                    <ChildExt handleForUpdate = {handleForUpdate.bind(this)} /></div>)
    }
}

if(document.querySelector("#demo")){
    ReactDOM.render(
        <ParentExt />,
        document.querySelector("#demo")
    );
}

Regardez JSFIDDLE

romain
la source
0

Si vous travaillez avec un composant de classe en tant que parent, un moyen très simple de passer un setState à un enfant consiste à le transmettre dans une fonction de flèche. Cela fonctionne car il définit un environnement hissé qui peut être transmis:

class ClassComponent ... {

    modifyState = () =>{
        this.setState({...})   
    }
    render(){
          return <><ChildComponent parentStateModifier={modifyState} /></>
    }
}
Julio Pereira
la source