I have a parent jsx component that has 2 different jsx components within it. The one component is a button and the other is a div that opens and closes itself when you click on it (it has a click handler and a state of open or closed). I now want to add the ability for the button to open and close the div as well. Is the only way to accomplish this is to pass a handler function down to the button from the parent, moving the div’s open and closed state to the parent component, and pass the state down to the div as props? The reason I ask is that this particular div component is used in a number of different components and removing the open and closed state would affect a lot of different parent components.
Asked
Active
Viewed 291 times
0
-
1You can pass it an isOpen prop and on componentWillReceiveProps set the state inside the component. Thats a way to leave the functionality to the component but also affect it through it's parent. – Matan Bobi Mar 14 '18 at 11:06
-
Your usecase is a perfect case for lifting the state up, and even though it might require some changes in your app you should go ahead with it. Check this question on when to lift the state up https://stackoverflow.com/questions/46594900/reactjs-lifting-state-up-vs-keeping-a-local-state/47349693#47349693 – Shubham Khatri Mar 14 '18 at 11:08
-
"Is the only way to accomplish this is to pass a handler function down to the button from the parent, moving the div’s open and closed state to the parent component, and pass the state down to the div as props?" No you can use the strategy to allow external manipulation as suggested by @MatanBobi – Kunukn Mar 14 '18 at 13:24
2 Answers
1
Here's a code example of allowing external state manipulation where you can mix the usage of the button or the div to toggle the state. You extend your Collapsible component to use passed props to update the state.
class Collapsible extends React.Component {
constructor(props){
super(props);
this.state = { isOpen: this.props.isOpen !== false };
this.toggleOpen = this.toggleOpen.bind(this);
}
componentWillReceiveProps({ isOpen }) {
this.setState({ isOpen });
}
toggleOpen(){
this.setState((prevState) => ({ isOpen: !prevState.isOpen }))
}
render() {
let display = this.state.isOpen ? null : "none";
return (
<div
className="collapsible"
onClick={this.toggleOpen}
>
<header> header </header>
<div style={{ display }}>{this.props.children}</div>
</div>
);
}
}
class Parent extends React.Component {
constructor(props){
super(props);
this.state = { isOpen: true };
this.toggleOpen = this.toggleOpen.bind(this);
}
toggleOpen(){
this.setState((prevState) => ({ isOpen: !prevState.isOpen }))
}
render() {
return (
<div className="parent">
<Collapsible isOpen={this.state.isOpen}>content</Collapsible>
<button onClick={this.toggleOpen}>
toggle
</button>
</div>
);
}
}

Kunukn
- 2,136
- 16
- 16
0
Here is another code example, hope it helps:
I use the local state of the Container
and pass this down to the child components. In a bigger app I'd advice you to use something like Redux to manage your state.
The central idea is that the parent component passes a function which can "change it's state" to the button child. It also passed the current isOpen
state to the panel. Clicking the button will change the state of the parent, thus triggering a reflow, thus updating the collapsable.
For future reference:
import React from "react";
import { render } from "react-dom";
const Collapsable = ({ isOpen }) =>
isOpen ? (
<div
style={{
border: "1px solid orange",
padding: "1rem",
background: "maroon",
color: "white"
}}
>
{" "}
Hey, I'm open{" "}
</div>
) : (
<div>Oh no...closed :(</div>
);
const Button = ({ openPanel }) => (
<button onClick={() => openPanel()}>Open Panel</button>
);
class Container extends React.PureComponent {
state = { open: false };
openPanel = () => {
this.setState({
...this.state,
open: this.state.open ? false : true
});
};
render() {
return (
<div>
<Button openPanel={this.openPanel} />
<Collapsable isOpen={this.state.open} />
</div>
);
}
}
const App = () => (
<div>
<Container />
</div>
);
render(<App />, document.getElementById("root"));

Mr. Baudin
- 2,104
- 2
- 16
- 24
-
@mr-baudin You shouldn't be using arrow functions inside a render as it creates a new function on each render. instead, bind the function in the constructor or use curry to bind. Also, there's no need to spread the last state when your setting the new state is setState changes only the properties it needs to change and when you are editing the previous state you should be using `setState((prevState) => ({isOpen: !prevState.isOpen}))` – Matan Bobi Mar 21 '18 at 11:05
-
@MatanBobi Thanks for the feedback, you are right. Just for some bookkeeping: [don't use arrow functions in render](https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36). You are also right about the state. I didn't know that thank you! – Mr. Baudin Mar 21 '18 at 13:18