Is there a way to avoid passing the same handler as a prop to children component every time ?
I create a Popup component and i want it to update the sate before closing
closePoPup = (e) => {
console.log('closePoPup');
e.preventDefault()
this.setState({
renderProgramePoPup: false,
renderTreeListPoPup: false,
})
}
.....
<Popup title="..." classes="..." closePoPup={this.closePoPup}>
<Foo ... />
</Popup
But i have to put the prop closePoPup={this.closePoPup} in every component like bellow :
class ProjectForm extends React.Component {
componentWillMount() {
this.setState({
renderProgramePoPup: false,
renderTreeListPoPup: false,
});
}
....
closePoPup = (e) => {
e.preventDefault()
this.setState({
renderProgramePoPup: false,
renderTreeListPoPup: false,
})
}
....
render() {
return (
<div>
...
{(this.state.renderProgramePoPup ?
<Popup title="..." classes="..."
closePoPup={this.closePoPup}>
<SimplePopUp ... />
</Popup> : null
)}
{(this.state.renderTreeListPoPup ?
<Popup title="..." classes="..."
closePoPup={this.closePoPup}>
<TreeList ... />
</Popup> : null
)}
</div>
)
}
}
You can construct a wrapper:
render() {
const PopupWithCloseProp = props => <Popup closePoPup={this.closePoPup} {...props} />;
return (
<div>
<PopupWithCloseProp someProp />
<PopupWithCloseProp someDiffProp />
</div>
);
}
Or a generic factory if you have different types of components needing closePoPup:
const createComponentWithCloseProp = Component => props => (
<Component closePoPup={this.closePoPup} {...props} />
);
const PopupWithCloseProp = createComponentWithCloseProp(Popup);
One potential solution is to create a list or object this.state.renderPopUp. Then you can map() over the list (or Object.keys()) to create a list of Popups. You can extend this idea even further by including data for each Popup in the list.
Related
Let's say I have this component:
class MyForm {
render() {
return (
<>
<h3>Remember my Setting</h3>
<Toggle
value={this.state.rememberMe}
onClick={() => {
this.setState(prevState => ({ rememberMe: !prevState.rememberMe }));
}}
/>
</>
)
}
}
How can I pass this prevState if its wrapped in child component? Is this acceptable?
class MyForm {
render() {
return (
<PartialForm
onClick={(v) => {
this.setState({ rememberMe: v });
}}
/>
)
}
}
const PartialForm = (props) => {
return (
<>
<h3>Remember my Setting</h3>
<Toggle
value={props.rememberMe}
onClick={() => {
props.onClick(!props.rememberMe);
}}
/>
</>
);
}
I want to know if accessing props.rememberMe is the same and safe as accessing prevState.rememberMe in parent component. Thanks
In my opinion, the way you're doing is safe and it has no conflicts at all.
I can simply demonstrate those re-rendering steps here:
Pass the current state to PartialForm
Trigger toggle click props.onClick for updating rememberMe in the upper component
The upper component gets re-rendered, and then your child component will have the latest state of rememberMe
By the way, you also forget to pass rememberMe in <PartialForm />
class MyForm {
render() {
return (
<PartialForm
rememberMe={rememberMe}
onClick={(v) => {
this.setState({ rememberMe: v });
}}
/>
)
}
}
const PartialForm = (props) => {
return (
<>
<h3>Remember my Setting</h3>
<Toggle
value={props.rememberMe}
onClick={() => {
props.onClick(!props.rememberMe);
}}
/>
</>
);
}
i have created three components Aboutus,AboutusChild & GrandChild and now i want to pass grandchild state value in my grandparent component that is "Aboutus" component but without using intermediate component(AboutusChild), is it possible in react js without using redux state management library.
i dont' want to use redux right now until some data-communication concept are not clear.
class AboutUs extends Component {
constructor(props) {
super(props)`enter code here`
this.state = {
childState: false
}
}
myMehtod(value) {
this.setState({
childState: value
})
}
render() {
console.log('AboutUs', this.state.childState)
return (
<div>
<h1 className={`title ${this.state.childState ? 'on' : ''}`}>
About us {this.props.aboutusProp}
</h1>
<AboutUsChild />
</div>
)
}
};
class AboutUsChild extends Component {
myMehtod(value) {
this.setState({
childState: value
},
this.props.myMehtodProp(!this.state.childState)
)
}
render() {
console.log('AboutUsChild', this.state.childState)
return (
<div>
<h1 className={`title ${this.state.childState ? 'on' : ''}`}>About Us Child</h1>
<GrandChild>
without function
</GrandChild>
<h1>About Us Child</h1>
<GrandChild Method={true} myMehtodProp={this.myMehtod.bind(this)} />
</div>
)
}
};
class GrandChild extends Component {
constructor() {
super()
this.state = {
TitleClick: false
}
}
TitleClick() {
this.setState(
{
TitleClick: !this.state.TitleClick
},
this.props.myMehtodProp(!this.state.TitleClick)
)
}
render() {
console.log('GrandChild', this.state.TitleClick)
return (
<div>
{this.props.Method ?
<h1 onClick={this.TitleClick.bind(this)} className={`title ${this.state.TitleClick ? 'on' : ''}`}>Grand Child</h1>
: null}
<h1>{this.props.children}</h1>
</div>
)
}
};
There's really no way to pass child state up without passing it to some callback. But frankly speaking this does not seem like a good design choice to me. You should always have common state on top of all components consuming that state. What you can do, is to pass stuff as children:
class SomeComponent extends React.Component {
...
render() {
const { value } = this.state;
return (
<Child value={value}>
<GrandChild value={value} onChange={this.handleValueChange} />
</Child>
);
}
}
const Child = ({ value, children }) => (
<div>
<h1 className={`title ${value ? 'on' : ''}`}>About Us Child</h1>
{children}
</div>
)
const GrandChild = ({ value, onChange }) => (
<div>
<h1 onClick={onChange} className={`title ${value ? 'on' : ''}`}>Grand Child</h1>
</div>
);
This way you got control from parent component of everything. If this is not the way, because you are already passing children, and for some reason you want to keep it this way, you can pass "render" prop:
// JSX in <SomeComponent /> render function:
<Child
value={value}
grandChild=(<GrandChild value={value} onChange={this.handleValueChange} />)
>
Some other children
</Child>
...
const Child = ({ value, grandChild, children }) => (
<div>
<h1 className={`title ${value ? 'on' : ''}`}>About Us Child</h1>
{grandChild}
{children}
</div>
)
If you want to be more fancy and there will more than few levels of nesting, you can always use context (highly recommend reading docs before using):
const someContext = React.createContext({ value: true, onChange: () => {} });
class SomeComponent extends React.Component {
...
render() {
const { value } = this.state;
return (
<someContext.Provider value={{ value: value, onChange: this.handleValueChange }}>
<Children>
</someContext.Provider>
);
}
}
...
const SomeDeeplyNestedChildren = () => (
<someContext.Consumer>
{({ value, onChange }) => (
<h1 onClick={onChange}>{value}</h1>
)}
</someContext.Consumer>
)
I would pick first two, if your structure is not that complex, but if you are passing props deeply, use context.
The only way to do something of this sort without external library would be leveraging React's Context API, although bare in mind that it is not nearly as robust as redux or mobX.
I'm using Gatsby for a static website.
My page is composed of two parts. Section 1 and Section 2.
I want to hide an image in Section 1, when a button is hovered in Section 2.
If I clean up a bit my .js, it looks like that :
<section>
<SomeText/>
<DefaultImage />
<ImageOne />
<ImageTwo />
</section>
<section>
<Button1/>
<Button2/>
</section>
What I want to achieve:
By default, <DefaultImage/> is shown.
If I hover <Button1>, I want to hide <DefaultImage/> and display <ImageOne/> instead.
Same goes for <Button2/>, which, when hovered, should hide <DefaultImage/> and display <ImageTwo/>.
I've read about onMouseEnter and onMouseLeave, and I think that the answer lies there but couldn't make it work for now.
Thank you for your ideas!
Maybe I can also pass a prop (like a css class) on the "to be hidden" component when the other is hovered
I managed to do it (check the accepted answer).
Here is my edited code:
class Parent extends Component {
state = {
isHoveringImage1: false
}
state = {
isNotHovering: false
}
state = {
isHoveringImage2: false
}
startHoverMasque = () => this.setState({ isHoveringMasque: true, isNotHovering: true})
stopHoverMasque = () => this.setState({ isHoveringMasque: false, isNotHovering: false })
startHoverMains = () => this.setState({ isHoveringMains: true, isNotHovering: true})
stopHoverMains = () => this.setState({ isHoveringMains: false, isNotHovering: false })
render() {
return (
<>
<Global
styles={globalStyles}/>
<section>
{
this.state.isNotHovering
? <ImageDefaultHidden />
: <ImageDefault/>
}
{
this.state.isHoveringImage1
? <Image1 />
: <ImageDefaultHidden />
}
{
this.state.isHoveringImage2
? <Image2 />
: <ImageDefaultHidden />
}
</section>
<section>
<Button1
onMouseEnter={ this.startHoverImage1}
onMouseLeave={ this.stopHoverImage1 }
>Bouton1</Button1>
<Button2
onMouseEnter={ this.startHoverImage2}
onMouseLeave={ this.stopHoverImage2 }
>Bouton 2</Button2>
</section>
</>
)
}
}
export default Parent```
You can annotate when the mouse enter and leaves the target Button in the state of your parent component:
class Parent extends Component {
state = {
isHovering: false
}
startHover = () => this.setState({ isHovering: true })
stopHover = () => this.setState({ isHovering: false })
render() {
return (
<>
<section>
<SomeText/>
{
this.state.isHovering
? <ImageOne />
: <DefaultImage />
}
<ImageTwo />
</section>
<section>
<Button1
onMouseEnter={ this.startHover }
onMouseLeave={ this.stopHover }
/>
<Button2/>
</section>
</>
)
}
}
The solution is to include the variable saying whether or not your image should be rendered in your parent component's state.
To set this variable, pass down a function to the component containing the button and bind it to the events you gave in your question : onMouseEnter and onMouseLeave.
Working example :
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
hideImage: false
}
}
toggleImage = hideImage => ev => {
this.setState({ hideImage })
}
render = () => {
return(
<div>
<ButtonComponent hovered={this.toggleImage}/>
<ImageComponent isHidden={this.state.hideImage}/>
</div>
)
}
}
const ButtonComponent = ({ hovered }) => <button onMouseEnter={hovered(true)} onMouseLeave={hovered(false)}>Hover me :)</button>
const ImageComponent = ({ isHidden }) => <img hidden={isHidden} src='https://reactjs.org/logo-og.png'/>
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.1/umd/react-dom.production.min.js"></script>
<div id='root'>
I'm doing a very simple two button state. where if i click abutton, A component displays and if bbutton is clicked then component B. I'm mapping through array of items so that each of them have their own buttons state. Lets say if I click item 1's button B then I want only first Item B to show. Right now All of them gets triggered at once. I have bounded each of them in the constructor but still i'm unable to get only the once the once clicked to trigger and show the relevant component.
class Home extends Component {
constructor(props) {
super(props);
this.state = {
lists: []
showA: true,
showB: false
}
this.aButtonHandler = this.aButtonHandler.bind(this);
this.bButtonHandler = this.bButtonHandler.bind(this);
}
aButtonHandler = (e) => {
this.setState({
showA: true,
showB: false
})
}
bButtonHandler = (e) => {
this.setState({
showA: false,
showB: true
})
}
render(){
return (
<div>
{this.state.lists.map(detail =>
<li>{detail.id}</li>
<button onClick={(e) => this.aButtonHandler(e)} >see A</button>
<button onClick={(e) => this.bButtonHandler(e)} >see B</button>
{this.state.showA ?
<ComponentA /> : null
}
{this.state.showB ?
<ComponentB /> : null
}
)}
</div>
)
}
If you are using arrow functions no need to bind functions.
If you want to bind then change it to normal function like this.
aButtonHandler(e){...}
bButtonHandler(e){...}
If you want to use bind in constructor no need to use arrow function, just use regular functions and pass the function directly to onClick
aButtonHandler(e) { this.setState({ showA: true, showB: false }); }
bButtonHandler(e) { this.setState({ showA: false, showB: true }); }
render() {
return (
<div>
{this.state.lists.map(detail => (
<div>
<li>{detail.id}</li>
<button onClick={this.aButtonHandler}>see A</button>
<button onClick={this.bButtonHandler}>see B</button>
{this.state.showA ? <ComponentA /> : null}
{this.state.showA ? <ComponentB /> : null}
</div>
))}
</div>
);
how to pass an argument in props using functional component, here I had given my worked example code,
Let me explain, My click event will trigger from PopUpHandle when I click on the PopUpHandle I need to get the value from ContentSection component. ContentSection will be the listing, when clicking on each listing want to get the value of the current clicked list. I tried with this code my console printed undefined but I don't know how to handle with functional component.
class mainComponent extends Component {
constructor(props) {
super(props);
this.popTrigger = this.popTrigger.bind(this);
}
popTrigger(data){
console.log(data);
}
render(){
return(
<Popup popTrigger={this.popTrigger} />
)
}
}
export default mainComponent;
Popup component
const PopUpHandle = ({ popTrigger, value}) => <li onClick={popTrigger.bind(this, value)} />;
const ContentSection =({popTrigger, value}) =>(
<div>
{value === 'TEST1' && (
<div>
<PopUpHandle popTrigger={popTrigger} value={value} />
</div>
</div>
)}
{value === 'TEST2' && (
<div>
<PopUpHandle popTrigger={popTrigger} value={value} />
</div>
</div>
)}
</div>
)
const ContentList = (({ items, popTrigger}) => (
<div>
{items.map((value, index) => (
<ContentSection
key={`item-${index}`}
popTrigger={popTrigger}
index={index}
value={value}
/>
))}
</div>
)
);
class example extends Component {
constructor(props) {
super(props);
this.state = {
items: ['TEST1', 'TEST2', 'TEST3', 'TEST4'],
};
this.popTrigger = this.popTrigger.bind(this);
}
popTrigger(){
this.props.popTrigger()
}
render(){
return(
<ContentList popTrigger={this.popTrigger} items={this.state.items} />
)
}
}
export default example;
popTrigger(data){
console.log(data);
}
You didn't pass the data while calling this.props.popTrigger(). In javascript if you didn't pass the arguments, it will consider it as undefined.
The ContentSection component is not passed a value prop and hence its not passed on to the PopUpHandle component. Pass it like
render(){
return(
<ContentSection popTrigger={this.popTrigger} value={"test1"} />
)
}