Pablo Fernandez
Pablo Fernandez

Reputation: 287380

How to have the children affect the parent in React?

I have a situation where I have something like this:

<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>

The DropDown component is one that I wrote, that renders this.props.children in a drop-down fashion. The DropDown has an onClick call that makes it close.

DropDown looks something like this (simplified):

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();
      }
    });
  };
}

The issue I'm running into is that I want to do something different to the DropDown whether it was closed selecting an option or not. How do I go about doing that?

Upvotes: 1

Views: 128

Answers (3)

Pablo Darde
Pablo Darde

Reputation: 6402

UPDATED

Accordingly @pupeno (and he is right), the dropdown logic should be within the dropdown itself. However, we should pass a callback function in order to deal with the chosen data.

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')
);
.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;
}
<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>

Upvotes: 1

azium
azium

Reputation: 20614

In your DropDown component you have some state and you are probably rendering your children like this:

this.props.children

Instead you can use render props to pass state, methods or anything else down to your children without having to handle it outside of the DropDown component at the parent level.

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)
  }
}

Then modify your render slightly:

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

Upvotes: 1

Schw2iizer
Schw2iizer

Reputation: 845

You could use an onChange function and call the parent function that is passed down as a prop. This will "affect the parent" as you asked.

this.props.onChange() or something similar.

Upvotes: 0

Related Questions