React: Children onclick to change parent's state for re-rendering - reactjs

I'm really new with react. I'm trying to get the parent to change page displayed depending on the states. I have a button in my sub-component that should send "true" or "false" to the parent component so it knows if to render it or not. I think it should be done with props like this:
this.state = {
btnNewScreen: this.props.btnNewScreen //true or false
};
But im not getting it to work. Could you give any tips? Here is the full parent - child
parent - maindisplay.js
import React from 'react';
import Mainpage_Addscreen from '../components/subcomponents/mainpage-addscreen';
import Mainpage_Showscreens from '../components/subcomponents/mainpage-showscreens';
//
class MainDisplay extends React.Component {
constructor() {
super();
this.state = {
btnNewScreen: false //should be this.props.btnNewScreen?
};
}
render() {
var renderThis;
if (!this.state.btnNewScreen) {
renderThis =
<div>
<Mainpage_Addscreen />
<Mainpage_Showscreens />
</div>
}
else {
//renderThis = <AddScreen />
renderThis =
<div>
<Mainpage_Addscreen />
<h3>Change to this when true (button click)</h3>
</div>
}
return (
<div>
{renderThis}
</div>
);
}
}
export default MainDisplay;
child - mainpage-addscreen.js
import React from 'react';
import Glyphicon from 'react-bootstrap/lib/Glyphicon';
import Button from 'react-bootstrap/lib/Button';
class Mainpage_Addscreen extends React.Component {
constructor() {
super();
this.state = {
btnNewScreen: true
};
this.newScreen = this.newScreen.bind(this);
}
newScreen(e) {
this.setState({ btnNewScreen: !this.state.btnNewScreen });
console.log(this.state.btnNewScreen);
}
render() {
var text = this.state.btnNewScreen ? 'Add new' : 'Screens';
return (
<div className="main_window col-sm-offset-1 col-sm-10">
<h3 id="addscreens">Screens: </h3>
<Button id="addScreen" className="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" onClick={this.newScreen}><Glyphicon id="refresh_screens" glyph="plus" /> {text}</Button>
</div>
);
}
}
export default Mainpage_Addscreen;

What you need to do is to pass a method from the parent to the child, That it can call when the button is clicked. This method that belongs to the parent will change the state.
In MainPage.js
changeButtonState(event) {
this.setState({btnNewScreen: !this.state.btnNewScreen})
}
pass this method to your child component as
<Mainpage_Addscreen buttonClick={this.changeButtonState.bind(this)} />
and finally in the child component,
<Button .... onClick={this.props.buttonClick} />

What you probably need is a callback function, which your parent passes as a prop to your child, and that your child can then call.
Something like this:
// In parent
constructor() {
...
this.onChildClicked = this.onChildClicked.bind(this);
}
onChildClicked() {
this.setState({childWasClicked : True });
}
render() {
...
return (
<div>
<Child onClicked={this.onChildClicked} />
</div>
)
}
// In child
render() {
...
return (
<div>
<button onClick={this.props.onClicked} />
</div>
)
}
PS: I notice that you have a capitalized <Button> component. This is typically for components that you have defined yourself. Standard DOM elements take lowercase, e.g. <div>, <p> etc

Related

How to access function from different components React

Here's the code for Panel
`
import React from "react";
// import {render} from "react-dom";
import AddInventory from "components/AddInventory";
class Panel extends React.Component{
constructor(props) {
super(props);
this.state = {
activeIndex: ''
}
}
componentDidMount() {
this.activePanel();
}
closePanel=()=>{
this.setState({
activeIndex : false
})
}
activePanel = ()=>{
this.setState({
activeIndex : true
})
}
render(){
return(
<div>
{/*<button className={"button is-primary add-btn"} onClick={this.activePanel}>add</button>*/}
<div className={this.state.activeIndex ? 'panel-wrapper active':'panel-wrapper'}>
<div className={"over-layer"}>
<div className={"panel"}>
<div className={"head"}>
<span onClick={this.closePanel} className={"close"}>x</span>
<AddInventory></AddInventory>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default Panel;
Products:
import React from "react";
import ToolBox from "components/ToolBox";
import Product from "components/Product";
import axios from 'components/axios'
import {CSSTransition , TransitionGroup} from 'react-transition-group'
import Panel from "components/Panel";
class Products extends React.Component{
product =[];
source =[];
state ={
product : [{
id:'1',
name:'Air Jordan1',
tags:'45 colours',
image:'images/1.jpg',
price:'21000',
status:'available'
},
{
id:'2',
name:'Nike Pual George PG 3',
tags:'45 colours',
image:'images/2.jpg',
price:'11000',
status:'available'
},
{
id:'3',
name:'Jordan Why Not Zer0.2',
tags:'10 colours',
image:'images/3.jpg',
price:'15000',
status:'unavailable'
},
]
}
componentDidMount() {
// fetch('http://localhost:3003/products').then(response => response.json()).then( data=>{
// console.log(data)
// this.setState({
// product : data
// })
// })
axios.get('/products').then(response => {
this.setState( {
product : response.data,
source : response.data
})
})
}
search = text=>{
//1.get a new array from product
let _product = [...this.state.source]
//2.filter the array
let res = _product.filter((element)=>{
return element.name.toLowerCase().includes(text.toLowerCase())
})
//set state
this.setState({
product : res
})
}
add = ()=>{
let panel = new Panel(this.props)
panel.activePanel()
}
// add =()=>{
// panel.setState({
// activeIndex : true
// })
// }
render() {
return(
<div>
<ToolBox search={this.search}/>
<div className={'products'}>
<div className="columns is-multiline is-desktop">
<TransitionGroup component={null}>
{
this.state.product.map(p=>{
return (
<CSSTransition
timeout={400}
classNames="product-fade"
key={p.id}
>
<div className="column is-3" key={p.id}>
<Product product={p}/>
</div>
</CSSTransition>
)
})
}</TransitionGroup>
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
</div>
<button className={"button is-primary add-btn"} onClick={this.add}></button>
</div>
</div>
)
}
}
export default Products;
I was trynna use activePanel() in Products but it gives me : Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign tothis.statedirectly or define astate = {};` class property with the desired state in the Panel component.
I tried initialize a new panel() but it still gives me the same error.
welcome. I don't think this approach is best practice. Generally, components should only ever be updating their own state (see here) and typically you want data to flow from parent component to child component (see here). Additionally, your design is deceptive. When you render a component, you declare it as JSX in some render (or return) statement. But here, Panel is never formally instantiated in JSX.
In Panel, I would suggest watching a prop such as active via shouldComponentUpdate and updating state based on changes to that prop. Then in Products you can instantiate an instance of Panel in JSX and dynamically set the value of that prop.

Handle multiple child component in React

I've tried to look everywhere and couldn't find anything related to my use case, probably I'm looking for the wrong terms.
I have a situation where I have a bar with 3 icons, I'm looking for set one icon "active" by changing the class of it.
The icon is a custom component which have the following code
export default class Icon extends Component {
state = {
selected : false,
}
setSelected = () => {
this.setState({
selected : true
})
}
setUnselected = () => {
this.setState({
selected : false
})
}
render() {
var classStatus = '';
if(this.state.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return <div className={classStatus} onClick={this.props.onClick}><FontAwesomeIcon icon={this.props.icon} /></div>
}
}
In my parent component I have the following code
export default class MainPage extends Component {
handleClick(element) {
console.log(element);
alert("Hello!");
}
render() {
return (
<div className="wrapper">
<div className="page-header">
<span className="menu-voice">File</span>
<span className="menu-voice">Modifica</span>
<span className="menu-voice">Selezione</span>
</div>
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon icon={faFileCode} onClick={() => this.handleClick(this)} />
<Icon icon={faCodeBranch} onClick={() => this.handleClick(this)} />
<Icon icon={faMagnifyingGlass} onClick={() => this.handleClick(this)} />
</div>
</span>
<span className="files-section">Files</span>
<span className="editor-section"></span>
</div>
<div className="page-footer">
Footer
</div>
</div>
);
}
}
What I'm trying to achieve is that when one of the Icon child component get clicked it will set the selected state to true manage by the parent component, in the same time while one of them is true I would like that the parent would set to false the other twos.
I've tried to use the useRef function but it doesn't look as a best practise.
Which is the correct way to do it? Sending also this to the handleClick function it just return the MainPage class instead of the child. Any suggestion at least where I should watch?
Thanks in advance
I suggest not storing the state in the icon, since it doesn't know what else you're using it for. Simply have the icon component take it's 'selected' status from props. e.g.
export default class Icon extends Component {
render() {
var classStatus = '';
if(this.props.selected)
classStatus = "selected-icon"
else
classStatus = "icon"
return (
<div className={classStatus} onClick={this.props.onClick}>.
<FontAwesomeIcon icon={this.props.icon} />
</div>
);
}
};
Then you can just manage the state in the parent where it should be:
export default class MainPage extends Component {
constructor(props) {
super(props);
this.state = { selectedOption : '' };
}
handleSelectOption(newValue) {
this.setState({ selectedOption: newValue });
}
isSelected(value) {
return value === this.state.selectedOption;
}
render() {
return (
<div className="wrapper">
{ /* etc... */ }
<div className="page-main">
<span className="icon-section">
<div className="top-icon">
<Icon
icon={faFileCode}
onClick={() => this.handleSelectOption("File")}
selected={isSelected("File")}
/>
<Icon
icon={faCodeBranch}
onClick={() => this.handleSelectOption("Modifica")}
selected={isSelected("Modifica")}
/>
{ /* etc... */ }
</div>
</span>
</div>
{ /* etc... */ }
</div>
);
}
};
You should define a constructor in your class component:
constructor(props) {
super(props);
this.state = { selected : false };
}
You also have to call a function which modify the state when you click on the Icon. onClick={this.props.onClick} doesn't change the state

Why is my class component not rendering correctly?

I've been at this awhile and I can't get my head around it. I feel so stupid. Can anyone tell me what's wrong?
The console log works currently, and the console.log of the Object states that the state has been updated from false to true.
class ChoiceBar extends React.Component {
constructor() {
super();
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
handleFirstQuestion() {
console.log("Start Rendering First Question")
this.setState({handleFirstQuestion: true}, () => {console.log(this.state);
});
}
render() {
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion.bind(this)}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
}
}
The first thing is that you are binding the function twice, one in constructor and one in onClick event handler.
Second pass props component in constructor and super
constructor(props) {
super(props);
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
I don't think you are exporting the class component
write this after the class component
export default ChoiceBar;
you should probably use aero functions and useState hook instead of the class component
for example:
const ChoiceBar = () => {
const [handleFirstQuestion, setHandleFirstQuestion] = useState(false)
}

Passing props / state back to parent component in React JS

I have two components, a select list with several options and a button component
What I'd like to do in the UI is that the user can select any option from the select list they want and then press the button to reset the list to 'select'
I'm keeping the parent component as the one source of truth - all updated states are passed back to the parent component, I've actually got this working great in the context of one file with the following code;
class SelectList extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onSelectListChange(e.target.value);
}
render() {
const selectedValue = this.props.selectedValue;
log.debug('SelectListValue(): ', this.props.selectedValue);
return (
<select value={selectedValue} onChange={this.handleChange}>
<option value='One'>One</option>
<option value='select'>select</option>
<option value='Three'>Three</option>
<option value='Four'>Four</option>
<option value='Five'>Five</option>
</select>
);
}
}
class SelectListReset extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onResetChange('select');
}
render() {
return (
<div>
<button onClick={this.handleChange}>Reset list to select</button>
</div>
);
}
}
class Page extends Component {
constructor(props) {
super(props);
this.state={
selectedValue: 'select'
}
this.handleSelectedListChange = this.handleSelectedListChange.bind(this);
this.handleResetChange = this.handleResetChange.bind(this);
}
handleSelectedListChange(selectedValue) {
this.setState({selectedValue});
}
handleResetChange() {
this.setState({selectedValue: 'select'});
}
render() {
log.debug('render(): ', this.props);
log.debug('ParentListValue(): ', this.state.selectedValue);
return (
<div className="content-area">
<div className="container">
<h1>{LANGUAGES_CONTROLLER.format(this.props.languages, 'TitleSettings')}</h1>
<div>
<SelectList
onSelectListChange={this.handleSelectedListChange}
selectedValue={this.state.selectedValue}
/>
<SelectListReset
onResetChange={this.handleResetChange}
selectedValue={this.state.selectedValue}
/>
</div>
</div>
</div>
);
}
But what I've actually like to do is move the reset button to it's own file and this is where I fall over trying to pass the props / state back to the parent.
So the render method would actually look like this
render() {
log.debug('render(): ', this.props);
log.debug('ParentListValue(): ', this.state.selectedValue);
return (
<div className="content-area">
<div className="container">
<h1>{LANGUAGES_CONTROLLER.format(this.props.languages, 'TitleSettings')}</h1>
<div>
<SelectList
onSelectListChange={this.handleSelectedListChange}
selectedValue={this.state.selectedValue}
/>
<TestComponent
onResetChange={this.handleResetChange}
selectedValue={this.state.selectedValue}
/>
</div>
</div>
</div>
);
}
I import TestComponent and then inside of TestComponent is where I will have my code for the SelectListReset component but the problem I'm having is that now the values are sent to it as props which should be immutable right so can't be updated?
That's where my understand stops .. if someone can point me in the right direction on this I'd be very grateful!
Props received from a parent will change if the relevant state in the parent is changed. There's no problem with this and all the re-rendering is handled by React.
You can pass props down as many levels as you like.

setState Warning in child component. Warning: Can only update a mounted or mounting component

I have a page which presents a list of projects, when a user clicks on that particular project a view component is called in the render().
Parent :
constructor(props) {
super(props);
this.state = {
showData : [],
view : false,
projectId: ''
};
this.buttonHandler = this.buttonHandler.bind(this);
this.back = this.back.bind(this);
};
// change view state to true to render diff component
buttonHandler(){
this.setState({view:true})
};
back(){
this.setState({view:true})
}
render(){
let compA = (
<Paper>
<List>
<Subheader >New
Projects</Subheader>
{this.state.showData.map(item =>
<div key={item.title}>
<ListItem onClick={()=>
this.buttonHandler()} leftAvatar=
{<Avatar icon={<Wallpaper />} />} primaryText=
"test" secondaryText="test" />
<Divider inset={true} />
</div>
)}
</List>
</Paper>
);
let compB = (
<ReviewProject
back={this.back}/>
);
return(
<div>
{this.state.view?compB:compA}
</div>
);
}
child comp B :
constructor(props) {
super(props);
this.state = {
//some code
};
}
//calls function back from parent which sets state the "view" to false
dismiss() {
this.props.back();
};
When the dismiss() function is called at the child to render back the list component compA, a warning pops out:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the compB component.
Is there a way to solve this? and what is a good practice to navigate from one component to the other back and forth
Because actually on first render, it's kind of if compA is rendered, then compB is missing, therefore when the react tries to re-render, it cannot find the missing element.
==> SOLUTION:
render(){
let returnedComp = (
<Paper>
<List>
<Subheader >New Projects</Subheader>
{this.state.showData.map(item =>
<div key={item.title}>
<ListItem onClick={()=>
this.buttonHandler()} leftAvatar=
{<Avatar icon={<Wallpaper />} />} primaryText=
"test" secondaryText="test" />
<Divider inset={true} />
</div>
)}
</List>
</Paper>
);
if (this.state.view) {
returnedComp = (
<ReviewProject back={this.back}/>
);
}
return(
<div>
{returnedComp}
</div>
);
}
You can try using ref also before setting your state as defined below
function(){
if (this.refs.ref)
this.setState({view: true});
}
render() {
return (
<div ref="ref">{this.state.view}</div>
);
}
The solution I found is:
To create a third component called viewProject component to handle the switching between A and B.
class A extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
This is component A
<button onClick={this.props.onGoBClick}>Go to B</button>
</div>
)
}
}
class B extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
This is component B
<button onClick={this.props.onGoAClick}>Go to A</button>
</div>
)
}
}
class ViewContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
view: false,
}
this.goToA = this.goToA.bind(this);
this.goToB = this.goToB.bind(this);
}
goToA() {
this.setState({view: false})
}
goToB() {
this.setState({view: true})
}
render() {
return (
this.state.view ?
<B onGoAClick={this.goToA}/>
:
<A onGoBClick={this.goToB}/>
)
}
}
ReactDOM.render(<ViewContainer />, document.getElementById('app'))

Resources