onClick set state for parent of parent in React? - javascript

So I'm new to React and still learning, managing onClick from the parent element is easy but I'm not sure how I can then send that click up one or more steps to the parents of the parent. In my setup the components are like this Page > Header > Menu Button. The Header comp controls the menu opening and closing but for my animation purposes I need to set a state in the Page parent so it can pass it on to other components that are siblings of Header. When Menu Button is clicked I need to send it up the chain all the way to Page. Can someone please explain how this is achieved? Most examples I found just talk about a direct parent child onClick handle which I'm already doing with Header and Menu Button components.

See the following example...
Use a click handler that has been passed down through the react component inheritance chain.
function MenuButton(props) {
return <button onClick={props.onClick}>Do Something</button>
}
function Header(props) {
return (
<div>
<MenuButton onClick={props.onClick} />
</div>
)
}
Establish a click handler in the parent most component and pass as a prop to the child component like so...
class Page extends Component {
constructor() {
super();
this.state = {
someStateToCache: false
};
this.clickHandler = this.clickHandler.bind(this); //bind context to click handler
}
clickHandler() {
this.setState({ someStateToCache: true });
}
render() {
return (
<div>
<Header onClick={this.clickHandler} />
</div>
);
}
}

Related

Build Table from Button Click

New to JavaScript over the past two weeks and am attempting to build a table from a button click. Using react-bootstrap/Button and react-bootstrap-table-next libraries. I have three files that I'm working on:
File that holds the layout of the page
A ReactButton class
Table class
I can render the button on the page, but can't get the table to render on a click. On the layout page, I'm building the button with:
render() {
return (
<div>
<ReactButton
name="Reset Table"
onClick={this.buttonClick}
keyField={{keyField: 'keyField'}}
products={DataPopulation.sampleData()}
columns={DataPopulation.getFields()}
selectRowProp={{mode: 'checkbox'}}
/>
</div>
The button code is:
class ReactButton extends Component {
constructor(props) {
super(props);
this.state = {
clicked: false
};
this.buttonClick = this.buttonClick.bind(this);
}
async buttonClick() {
this.setState({
clicked: true
});
console.log(this.props);
console.log("Button part")
}
render() {
return (
<Button
variant="primary"
size="lg"
onClick={this.buttonClick}>
{this.props.name}
</Button>
)
}
}
export default ReactButton;
When I click the button, I can see the log of the properties (keyField, products, columns, etc) passed to the Button class. Is it possible to then pass these properties to a table class file and have it render the table? Table code with the react-bootstrap-table-next libraries would be something like this, I would imagine:
class MultiSelectTable extends Component {
constructor(props) {
super(props)
}
...
<BootstrapTable
keyField= { this.props.keyField }
data={ this.props.products }
columns={ this.props.columns }
selectRow={ this.props.selectRowProp }
/>
But I'm unsure how the flow should be to call this from the main layout page. Should I call the table build from the Button class? And how would I render the table?
Edit: Currently reviewing this for more info about it. It seems like the logic needs to be: the page renders the button, which when clicked renders the table. What is the preferred best practice for referencing and building hierarchical components?
You could do something like this in the parent component:
buttonClick(clicked) {
this.setState({
buttonClicked: clicked
})
}
render() {
return (
<>
<ReactButton
name="Reset Table"
onClick={this.buttonClick}
keyField={{keyField: 'keyField'}}
products={DataPopulation.sampleData()}
columns={DataPopulation.getFields()}
selectRowProp={{mode: 'checkbox'}}
/>
{this.state.buttonClicked ? <MultiSelectTable {...props} /> : null}
</>
)
}
And then in the button component you pass the click state back to the parent components click function:
buttonClick() {
this.props.onClick(true);
}
That way you make sure the table is rendered only when the button is clicked and all the props gets passed to the table component.

using a simple component (dumb) to create a list of buttons and use parent method

I'm trying to create a simple dashboard. I'm just exploring some new ideas I have in react and it's been so long I'm running into a strange problem I can't seem to understand.
I have a very simple class:
export default class Dashboard extends React.Component {
constructor(){
super();
}
HandleClick = (e) => {
if (e.name === "createEvent") {
console.log('event clicked');
}
console.log(e.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={this.HandleClick}/>
</div>
)
}
}
and then I have a simple function outside of the class that creates a button list:
function ButtonList(props) {
return (
<button name="createEvent" onClick={props.HandleClick}>Create Event</button>
)
}
the idea behind this was instead of having so much stuff inside one superclass I wanted to separate simple functionality, like a button or command list if you will, that opon clicking would eventually change the state of the navbar.
I'm not sure how I would return that values of the button, or aside from that pass a parameter into the button from a child prop.
For example instead of doing HandleClick = (e) => and actually look for a parameter, how would I pass that in the child function where it gets used (if there were many more buttons)?
This is what you should be doing instead:
On your parent component, you can use arrow functions to pass the parameters within handleClick. This will allow you to listen to the events on your child ButtonList component with the parameters passed onto the method.
In addition, if you want to access to name attribute of your button, you should be calling event.target.name, as name is part of the target property of the Event interface.
export default class Dashboard extends React.Component {
constructor(){
super();
}
handleClick = (e) => {
if (e.target.name === "createEvent") {
console.log('event clicked');
}
console.log(e.target.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={(e) => this.handleClick(e)} />
</div>
)
}
}
And on your ButtonList functional component, you should pass the onClick event to the onClick props which was defined as part of the ButtonList component.
function ButtonList(props) {
const onClick = (e) => {
props.onClick(e);
};
return (
<button name="createEvent" onClick={(e) => onClick(e)}>Create Event</button>
)
}
I have created a demo over here.

Modal element is not hiding [React & React - Bootstrap]

I've created modal with React Bootstrap. Despite the fact, that I'm using onHide function, nothing happens. Here's my modal:
<React.Fragment>
<Modal
{...this.props}
bsSize="large"
aria-labelledby="contained-modal-title-lg"
show={this.state.showModal}
onHide={this.handleHide}
>
<Modal.Body>
...
</Modal.Body>
<Modal.Footer>
<Button id = "closeModal" variant="danger" onClick=
{this.handleHide.bind(this)}>
Save and close
</Button>
</Modal.Footer>
</Modal>
</React.Fragment>
I'm passing "show" from other component, when click occurs on some element. onClick on that element is specified to: "showModal: true". Then I'm passing showModal to other component that is rendering different elements according to option choosed:
{this.state.addingState && (
<MyForm
{...this.state.item}
show={this.state.showModal}
...
/>
)}
At last in MyForm component I have function that passes props to component with modal:
createModalComponent {
...
let modalComponentProps= {
...
show: this.props.show,
}
So, this is the way "show" is going. In my file with modal component I have function for handling hide:
handleHide() {
this.setState({ showModal: false });
}
Now in this component showModal is initialize in state like so:
constructor(props) {
super(props);
this.state = {
showModal: this.props.show
};
this.handleHide = this.handleHide.bind(this);
}
I've tried many things. Other state variables, without initializing showModal in state and many more. When clicking on the button or beyond the modal, modal is still visible and not hiding. I will be very grateful for your help and/or suggestions how to fix this.
So, the way showModal is going: parent component (where this.state.addingState is happening) -> MyForm component (where let modalComponentProps= { show: this.props.show, ... happens) -> actual modal component
Code on CodePen
you have 2 possibilities: you can add the method to your parent and pass the method + the result of show like this (use same name of props and method for best practice, so you will be not confuse):
Parent
<Modal
{...this.props}
bsSize="large"
aria-labelledby="contained-modal-title-lg"
show={this.state.showModal}
handleHide={this.handleHide}
>
Child
<MyForm
show={this.props.showModal}
handleHide={this.props.handleHide}
/>
To use the modal from parent, you can call it like this in child: this.props.handleHide(true).
Or you let the child manage the state by itself, so you would place the method and state in child, not in parent (depending on the architecture).
It is not recommended to add the props in child state.
Also, you could use es6 function to avoid binding like this:
handleHide = () => this.setState({ showModal: false });
Look on the shouldComponentUpdate method
shouldComponentUpdate(nextProps) {
return !isEqual(this.props, nextProps);
}
You are checking only props but you are changing the state of the component. Fix the method or remove it and it will be working

componentWillReceiveProps in Vue

I am new pretty new to Vue, and coming from a rather React-y suburb. I am rebuilding my SideNav ("drawer") component from the latter. There, when one clicked the button (not being related to the navigation per se), it setStateed this.state.toggle that was tied to appropriate
class thePage extends React.Component {
...
this.handleToggleClick = this.handleToggleClick.bind(this);
this.state ={
toggleState: false
};
}
// Slide out buttons event handlers
handleToggleClick(){
this.setState({
toggleState: !this.state.toggleState
})
}
render() {
const button = <a href="#" onClick={this.handleToggleClick}>here</a>
const isOpenWithButton = this.state.toggleState;
return (
<div>
{button}
<SideNav logo="logo.png" isOpenWithButton={isOpenWithButton}>
. . .
</SideNav>
</div>
);
}
}
export default SideNavPage;
the SideNav looks as follows:
class SideNav extends React.Component {
constructor(props){
super(props);
this.state = {
isThere: false,
showOverlay: false,
}
this.handleOverlayClick = this.handleOverlayClick.bind(this);
}
componentWillReceiveProps(NextProps) {
if (this.props.isOpenWithButton !== NextProps.isOpenWithButton) {
this.setState({
isThere: true,
showOverlay: true
})
}
}
handleOverlayClick(){
this.setState({
isThere: false,
showOverlay: false
});
}
render() {
const {
tag: Tag,
...
isOpenWithButton,
} = this.props;
let isThere = this.state.isThere;
let showOverlay = this.state.showOverlay;
const overlay = <div class="overlay" onClick={this.handleOverlayClick}></div>
const sidenav = (
<Tag>
<ul>
{logo &&
<li>
<div className="logo-wrapper">
<a href={href}>
<img src={logo} className="img-fluid flex-center d-block"/>
</a>
</div>
</li>
}
{children}
</ul>
</Tag>
);
return (
<div>
{isThere && sidenav}
{showOverlay && overlay}
</div>
);
}
}
export default SideNav;
So, as you can see, clicking the button causes the isOpenWithButton props to change, and whenever it happens (componentWillReceiveProps), the sidenav with overlay appear.
I did some work on porting it to Vue, but as it lacks this lifecycle hook I am stuck with props. I have a following problem: clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change, what necessitates clicking the button twice if the sidenav has been already open. I know I must be missing a vital part in Vue logic, I just cannot grasp which.
Using .sync modifier
What you are looking for is called in vue a .sync modifier.
When a child component mutates a prop that has .sync, the value change will be reflected in the parent.
With this you can achive what you described:
clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change
Using a centralised store - (like vuex)
The same could also be achieved if you have a centralised state/store, in this case both of your components could rely on that state property.
See state management on Vue documentation:
Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them
You could simple toogle the same property, for example:
$store.commit('overlayToggle');

ReactJS keeping a single "Active" state between multiple components

I am attempting to keep with best practices, while adhering to the documentation. Without creating to many one-off methods to handle things for a maintainability standpoint.
Anyway all in all, I am trying to achieve a state between sibling elements that is in sorts an "active" state visually at the least. With something like jQuery I would simply do..
$(document).on('.nav-component', 'click', function(e) {
$('.nav-component').removeClass('active');
$(this).addClass('active');
});
However in react, each component in it of itself is independent of the next and previous, and should remain as such per the documents.
That said, when I am handling a click event for a component I can successfully give it a state of active and inactive, toggling it on and off respectively. But I end up in a place where I have multiple "active" elements when I don't need them as such.
This is for setting up a navigation of sorts. So I want the one in use at the moment to have that active class while the rest won't
I use an app.store with reflux to set state for multiple pages/components. You can do the same passing state up to a common component but using the flux pattern is cleaner.
class AppCtrlRender extends Component {
render() {
let page = this.state.appState.currentPage;
let hideAbout = (page != 'about');
let hideHome = (page != 'home');
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
<div id='allPageSty' style={allPageSty}>
<AboutPage hide={hideAbout} />
<HomePage hide={hideHome} />
</div>
</div>
);
}
}
let getState = function() { return {appState: AppStore.getAppState(),}; };
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount = () => { this.unsubscribe = AppStore.listen(this.storeDidChange); }
componentWillUnmount = () => { this.unsubscribe(); }
storeDidChange = () => { this.setState(getState()); }
}
In the page/component check for this.props.hide.
export default class AboutPage extends Component {
render() {
if (this.props.hide) return null;
return (
<div style={AboutPageSty}>
React 1.4 ReFlux used for app state. This is the About Page.
<NavMenu />
</div>
);
}
}
Siblings needing to share some sort of state in React is usually a clue that you need to pull state further up the component hierarchy and have a common parent manage it (or pull it out into a state management solution such as Redux).
For sibling components where only one can be active at a time, the key piece of state you need is something which lets you identify which one is currently active and either:
pass that state to each component as a prop (so the component itself can check if it's currently active - e.g. if each item has an associated id, store the id of the currently active one in a parent component and pass it to each of them as an activeId prop)
e.g.:
var Nav1 = React.createClass({
getInitialState() {
return {activeId: null}
},
handleChange(activeId) {
this.setState({activeId})
},
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
activeId={this.state.activeId}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
or use it to derive a new prop which is passed to each component (such as an active prop to tell each component whether or not it's currently active - e.g. in the id example above, check the id of each component while rendering it: active={activeId === someObj.id})
e.g.:
var Nav2 = React.createClass({
// ... rest as per Nav1...
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
active={this.state.activeId === item.id}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
The trick with React is to think of your UI in terms of the state you need to render if from scratch (as if you were rendering on the server), instead of thinking in terms of individual DOM changes needed to make the UI reflect state changes (as in your jQuery example), as React handles making those individual DOM changes for you based on complete renderings from two different states.

Categories

Resources