1
votes

Comment faire en sorte que les enfants affectent le parent dans React?

J'ai une situation où j'ai quelque chose comme ceci:

class DropDown extends Component {
  state = {
    open: false
  };

  render() {
    return (
      <div className={`drop-down ${this.state.open ? "open" : "closed"}`}>
        <div className="closed-version">
          <div className="header" onClick={this.open}>
            <div className="header-contents">Click here to select an option</div>
          </div>
        </div>
        <div className="open-version">
          <div className="content" onClick={this.closeWithoutSelection}>
            {this.props.children}
          </div>
        </div>
      </div>
    );
  }

  open = () => {
    this.setState({ open: true }, () => {
      if (this.props.onOpen) {
        this.props.onOpen();
      }
    });
  };

  closeWithoutSelection = () => {
    this.setState({ open: false }, () => {
      if (this.props.onCloseWithoutSelection) {
        this.props.onCloseWithoutSelection();
      }
    });
  };
}

Le composant DropDown est celui que j'ai écrit, qui rend this.props. enfants de manière déroulante. Le DropDown a un appel onClick qui le ferme.

DropDown ressemble à ceci (simplifié):

<DropDown>
  <p>Select an option:</p>
  <button onClick={() => console.log("opt 1")}>Option 1</button>
  <button onClick={() => console.log("opt 1")}>Option 2</button>
</DropDown>

Le problème que je rencontre est que je veux faites quelque chose de différent du DropDown, qu'il soit fermé en sélectionnant une option ou non. Comment procéder?


4 commentaires

Vous pouvez passer la fonction en tant qu'accessoire et gérer l'état dans le parent


Chaque bouton peut avoir un identifiant et un événement que vous faites => this.props.handleClick (event.target.id) sur onClick


@QuentinC: J'essaie de ne pas extraire l'état de savoir si la liste déroulante est ouverte ou non de la boîte de dépôt elle-même, car elle couplerait étroitement la boîte de dépôt au parent d'une manière sujette aux erreurs.


Vous n'avez pas besoin de coupler étroitement quoi que ce soit dans React. render props n'est qu'une solution que je recommande vivement. À l'avenir, les crochets sont probablement une solution plus soignée. Je peux modifier ma réponse pour inclure également les hooks si vous utilisez react 16.8 +


3 Réponses :


1
votes

MISE À JOUR

En conséquence @pupeno (et il a raison), la logique de la liste déroulante devrait être dans la liste déroulante elle-même. Cependant, nous devons passer une fonction de rappel afin de traiter les données choisies.

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
.drop-down {
  border: 1px solid black;
  width: 180px;
  height: 30px;
  overflow: hidden;
  transition: height 0.3s ease;
}

.open {
  height: 120px;
}

.drop-down > div {
  padding: 5px;
  box-sizing: border-box;
  height: 30px;
  border-bottom: 1px solid black;
}
class DropDown extends React.Component {
  state = {
    open: false,
  };
  
  toggleDropdown = (e) => {
    this.setState({
      open: !this.state.open,
    });
    
    const value = e.target.getAttribute('value');
    
    if ( value !== "null") {
      this.props.selectItem(value);
    }
  };
  
  render() {
    const { selectItem } = this.props;
    const { open } = this.state;
    
    return (
      <div className={open ? "drop-down open" : "drop-down"}>
        <div onClick={this.toggleDropdown} value="null">Select</div>
        <div onClick={this.toggleDropdown} value="1">Item 1</div>
        <div onClick={this.toggleDropdown} value="2">Item 2</div>
        <div onClick={this.toggleDropdown} value="3">Item 3</div>
      </div> 
    );
  }
};

class App extends React.Component {
  requestItem = (item) => {
    alert(`Request item ${item}`);
  };
  
  render() {
    return (
      <div>
        <DropDown selectItem={this.requestItem}/>
      </div>
    );
  }
}

ReactDOM.render(
  <App />,
  document.querySelector('#app')
);


2 commentaires

Je ne comprends pas très bien cette option. Je ne veux pas que le parent ait une logique de liste déroulante, je veux que les listes déroulantes soient autonomes. Imaginez une page dans laquelle nous récupérons N enregistrements de la base de données et créons N listes déroulantes.


Je @pupeno, j'ai mis à jour ma réponse pour montrer une solution fonctionnelle. J'ai gardé la logique déroulante encapsulée et lui ai donné une fonction de rappel. J'espère que cette aide.



0
votes

Vous pouvez utiliser une fonction onChange et appeler la fonction parent qui est transmise comme accessoire. Cela "affectera le parent" comme vous l'avez demandé.

this.props.onChange () ou quelque chose de similaire.


0 commentaires

1
votes

Dans votre composant DropDown , vous avez un état et vous rendez probablement vos enfants comme ceci:

<DropDown>
  {handleSpecialMethod =>
    <>
      <p>Select an option:</p>
      <button onClick={() => handleSpecialMethod("opt 1")}>Option 1</button>
      <button onClick={() => handleSpecialMethod("opt 1")}>Option 2</button>
    </>
  }
</DropDown>

À la place, vous pouvez utiliser render props pour passer l'état, les méthodes ou autre chose à vos enfants sans avoir à le gérer en dehors du composant DropDown à le niveau parent.

class DropDown extends Component {
  // constructor / state, methods...

  yourSpecialMethod(item) {
    // do your special thing here
  }

  render() {
    // pass state or methods down to children!
    return this.props.children(yourSpecialMethod)
  }
}

Puis modifiez légèrement votre rendu:

this.props.children


1 commentaires

Mmh, cela pourrait fonctionner. J'ai déjà des accessoires avec des composants dans le menu déroulant, ce serait juste un autre.