The code is as follows.
state = {
showSection: false,
selectedAccount: ''
};
The render method is defined as follows.
return (
<div>
<button onClick={this.selectAccount} className="btn btn-default">Select Account</button>
{
this.state.showSection &&
<div>
<AccountSelectionForm handleAccordionClick=
{this.handleAccordionClick}
selectedAccount={this.state.selectedAccount}
/>
</div>
}
</div>
)
selectAccount Method is defined as follows.
selectAccount = () => {
this.setState({selectedAccount: 'A123'})
}
AccountSelectionFormComponent
componentWillReceiveProps(nextProps) {
if(nextProps.selectedAccount) {
this.setState({
accountId: nextProps.selectedAccount
});
}
}
When the button is clicked, selectedAccount value is changed. The prop value also gets changed, since this is passed in the props.
In the AccountSelectionForm component i have written the componentWillReceiveProps method to set the selectedAccount in the state.
My assumption was that this would invoke componentWillReceiveProps method since the prop is changed.
The problem is that the componentWillReceiveProps method is not invoked.
Questions:
Is this issue due to AccountSelectionForm still not rendered in DOM(this.state.showSection is still false).
Any idea on how to fix this?
Related
As title.
What would I do to change state inside a callback function if I need to change state after I got some results from the back-end or files ,etc...?
Like this:
var strView="";
var CountIsPrime=function(InputNum){
IsPrime(InputNum,function(Res){
strView=Res;
});
};
export class TrialClass extends React.Component{
state={
DisplayString:strView
};
render(){
return <div>
<label>{strView}</label>
<button onclick={()=>CountIsPrime(Math.floor(Math.Random()*10000))}></button>
</div>
}
}
I am wondering how to change the value inside the label when I call CountIsPrime function?
Issues
strView isn't part of any React component state or props, so no amount of updating it will trigger a rerender to display updated values.
onclick might've been a typo, but it isn't valid.
Solution
Move the CountIsPrime callback definition into the component so it can update the state.
Render the state value into the label.
Use onClick handler.
Code
export class TrialClass extends React.Component{
state = {
strView: '',
};
countInPrime = inputNum => IsPrime(
inputNum,
strView => this.setState({ strView }),
);
render() {
const { strView } = this.state;
return (
<div>
<label>{strView}</label>
<button
type="button"
onClick={() => this.countInPrime(Math.floor(Math.Random() * 10000))}
>
X
</button>
</div>
);
}
}
I have a child component. It should create an object from props and render it. This object should get added as a state.
Below is the current code.
Example:-
<popupComponent element={object} />
popupComponent.js
class popupComponent extends Component {constructor(props) {
super(props);
this.state = {
name: ""
}
}
updateName (event) {
this.setState({
name: event.currentTarget.value
})
}
publishElement () {
this.props.saveAndClose({
name: this.state.name
});
this.setState({
name: ""
})
}
render() {
return (
<div draggable="true" >
<h4>Name:</h4>
<input id="elementName" type="text" placeholder="Enter element name" value={element.name} onChange={this.updateName.bind(this)}/>
<button id="saveAndClose" onClick={this.publishElement.bind(this)}>Save & close</button>
</div>
);
}
}
export default popupComponent;
Question: Which function other than render gets executed whenever state is changed? In this scenario constructor runs only once and I cannot try that because the time constructor gets executed, state isnt available.
Resolved issue by conditionally not creating the component at all.
Actual issue, Somehow this component's constructor was getting called only once but I wanted it getting called whenever it gets visually shown.
Resolved issue by conditionally not including the component at all as below.
{this.state.show ? <PopupMarkupEditor
element = {selectedElement}
saveAndClose = {this.saveElement}
show = {this.state.show}
/> : null }
I have a Parent component:
import React, { Component } from "react";
import { Button } from "./Button";
export class Dashboard extends Component {
constructor(props) {
super(props);
this.state = {
numbers: [],
disabled: false
};
this.setNum = this.setNum.bind(this);
}
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(prevState => ({
numbers: [...prevState.numbers, num]
}));
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums });
console.log(this.state.numbers);
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
render() {
return (
<div className="board-container">
<div className="board">
<div className="row">
<Button
id="1"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="2"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="3"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
<Button
id="4"
numbers={this.state.numbers}
onChange={this.setNum}
disabled={this.state.disabled}
/>
</div>
</div>
</div>
);
}
}
... and a Child component:
import React, { Component } from "react";
export class Button extends Component {
constructor(props) {
super(props);
this.state = {
isChecked: false
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
isChecked: !this.state.isChecked
});
var num = e.target.value;
this.props.onChange(num);
}
render() {
const { isChecked } = this.state;
if (isChecked === true) {
var bgColor = "#f2355b";
} else {
bgColor = "#f7f7f7";
}
let disabled = this.props.disabled;
if (this.props.numbers.includes(this.props.id)) {
disabled = false;
}
return (
<div className="number-container" id="checkboxes">
<label
className={!isChecked && disabled === false ? "num" : "checked-num"}
style={{ backgroundColor: bgColor }}
>
{" "}
{this.props.id}
<input
type="checkbox"
name={this.props.id}
value={this.props.id}
id={this.props.id}
onChange={this.handleChange}
checked={isChecked}
disabled={disabled}
/>
</label>
</div>
);
}
}
Whenever any Button component is clicked, the Parent component gets the child Button's id value and puts it into its numbers state array. Whenever a Button is unchecked, the Parent updates is numbers state by removing the id of the child Button.
If my code is right, the expected behavior is whenever a Button checkbox is clicked, the Parent numbers state will be updated immediately (adding or removing a number). However, it always updates with one step lag behind.
I know, that the issue is dealing with the React states not being updated instantly, and I've checked similar issues on Stackoverflow. The problem is that I can't figure it out how to make this two components interact with each other in a proper way. What would be the solution for this issue?
Here are three screenshots from codesandbox
If you want to play with it please find the link https://codesandbox.io/s/w2q8ypnxjw
What I did was, I basically copied and pasted your code and updated setNum function to reflect the changes Think-Twice suggested
setNum(num) {
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
} else if (this.state.numbers.includes(num)) {
let nums = [...this.state.numbers];
let index = nums.indexOf(num);
nums.splice(index, 1);
this.setState({ numbers: nums }, () => {
console.log("state logged inside else if", this.state.numbers);
});
}
if (this.state.numbers.length >= 4) {
this.setState({ disabled: true });
} else if (this.state.numbers.length < 4) {
this.setState({ disabled: false });
}
}
So before going further let's quickly address a couple of things regarding to React and setState
As B12Toaster mentioned and provided a link which contains a
quote from official documentation
setState() does not always immediately update the component. It may
batch or defer the update until later.
Think-Twice's also points out that by stating
Basically setState is asynchronous in React. When you modify a value
using setState you will be able to see the updated value only in
render..
So if you want to see the immediate state change in a place which
you trigger setState, you can make use of a call back function as
such setState(updater[, callback])
There are two approaches when it comes to and updater with setState,
you could either pass an object, or you could pass a function So in
Think-Twice's example, an object is passed as an updater
this.setState({ numbers: nums } //updater, () => {
console.log(this.state.numbers); //this will print the updated value here
});
When a function is used as an updater (in your setNum function you
already do that), the callback function can be utilized like below
if (!this.state.numbers.includes(num)) {
this.setState(
prevState => ({
numbers: [...prevState.numbers, num]
}),
() => {
console.log("state logged inside if", this.state.numbers);
}
);
}
Your current implementation and communication structure seems fine. It is actually called Lifting State Up which is recommended also by official documentation.
Basically you store the state of array numbers in a parent component (which can be considered as the source of truth) and you pass the method that changes the state as a prop to it's child component.
In the codesandbox link I provided, the functionalities works the way I expect (at least this is what I expect from your code)
Basically setState is asynchronous in React. When you modify a value using setState you will be able to see the updated value only in render. But to see updated state value immediately you need to do something like below
this.setState({ numbers: nums }, () => {
console.log(this.state.numbers); //this will print the updated value here
});
I have a parent component like below. I have a button here named View.
class DataTable extends Component {
constructor(props) {
super(props);
this.state = {
modalOpen: false,
};
this.view = this.view.bind(this);
}
view() {
this.setState({ modalOpen: true });
}
render() {
return (
<div>
<button className="mini ui button" onClick={this.view}>
<i className="user icon"></i>
View
</button>
<ModalBody modelStatus = {this.state.modalOpen}/>
</div>
)
}
}
I have a child component like below
class ModalBody extends Component {
state = { modalchildOpen: false }
componentDidMount() {
if(this.props.modelStatus) {
this.setState({ modalchildOpen: true })
console.log('yes')
}
else {
this.setState({ modalchildOpen: false })
console.log('no')
}
}
render() {
return (
<div>
<Modal open={this.state.modalchildOpen}/>
</div>
)
}
}
I would like to change status of modalchildOpenfrom false to true while clicking on Button View. In another action I would like to change status of modalchildOpenfrom true to false in the child component.
I agree with #lustoykov about how you would normally set the modal open/closed value through state. However, if you want to update the state based on props passed down from the parent, what you’re probably looking for is the componentWillReceiveProps life cycle method. This method runs anytime your child component receives props and you can compare the old props to the new props. You can then set the state inside that function.
Reference this link:
https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
Please note, there is newer version of this life cycle method called getDerivedStateFromProps. Be sure to check your versioning and see if you can use the new method as the old one will eventually become deprecated.
I solved the issue using below code.
componentWillReceiveProps(nextProps){
this.setState({
modalchildOpen: nextProps.modelStatus,
})
}
Thanks all.
I am trying to refactor the following code from my render view:
<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange.bind(this,false)} >Retour</Button>
to a version where the bind is within the constructor. The reason for that is that bind in the render view will give me performance issues, especially on low end mobile phones.
I have created the following code, but I am constantly getting the following errors (lots of them). It looks like the app gets in a loop:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
Below is the code I use:
var React = require('react');
var ButtonGroup = require('react-bootstrap/lib/ButtonGroup');
var Button = require('react-bootstrap/lib/Button');
var Form = require('react-bootstrap/lib/Form');
var FormGroup = require('react-bootstrap/lib/FormGroup');
var Well = require('react-bootstrap/lib/Well');
export default class Search extends React.Component {
constructor() {
super();
this.state = {
singleJourney: false
};
this.handleButtonChange = this.handleButtonChange.bind(this);
}
handleButtonChange(value) {
this.setState({
singleJourney: value
});
}
render() {
return (
<Form>
<Well style={wellStyle}>
<FormGroup className="text-center">
<ButtonGroup>
<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChange(false)} >Retour</Button>
<Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChange(true)} >Single Journey</Button>
</ButtonGroup>
</FormGroup>
</Well>
</Form>
);
}
}
module.exports = Search;
Looks like you're accidentally calling the handleButtonChange method in your render method, you probably want to do onClick={() => this.handleButtonChange(false)} instead.
If you don't want to create a lambda in the onClick handler, I think you'll need to have two bound methods, one for each parameter.
In the constructor:
this.handleButtonChangeRetour = this.handleButtonChange.bind(this, true);
this.handleButtonChangeSingle = this.handleButtonChange.bind(this, false);
And in the render method:
<Button href="#" active={!this.state.singleJourney} onClick={this.handleButtonChangeSingle} >Retour</Button>
<Button href="#" active={this.state.singleJourney} onClick={this.handleButtonChangeRetour}>Single Journey</Button>
I am giving a generic example for better understanding, In the following code
render(){
return(
<div>
<h3>Simple Counter</h3>
<Counter
value={this.props.counter}
onIncrement={this.props.increment()} <------ calling the function
onDecrement={this.props.decrement()} <-----------
onIncrementAsync={this.props.incrementAsync()} />
</div>
)
}
When supplying props I am calling the function directly, this wold have a infinite loop execution and would give you that error, Remove the function call everything works normally.
render(){
return(
<div>
<h3>Simple Counter</h3>
<Counter
value={this.props.counter}
onIncrement={this.props.increment} <------ function call removed
onDecrement={this.props.decrement} <-----------
onIncrementAsync={this.props.incrementAsync} />
</div>
)
}
That usually happens when you call
onClick={this.handleButton()} - notice the () instead of:
onClick={this.handleButton} - notice here we are not calling the function when we initialize it
THE PROBLEM is here: onClick={this.handleButtonChange(false)}
When you pass this.handleButtonChange(false) to onClick, you are actually calling the function with value = false and setting onClick to the function's return value, which is undefined. Also, calling this.handleButtonChange(false) then calls this.setState() which triggers a re-render, resulting in an infinite render loop.
THE SOLUTION is to pass the function in a lambda: onClick={() => this.handleButtonChange(false)}. Here you are setting onClick to equal a function that will call handleButtonChange(false) when the button is clicked.
The below example may help:
function handleButtonChange(value){
console.log("State updated!")
}
console.log(handleButtonChange(false))
//output: State updated!
//output: undefined
console.log(() => handleButtonChange(false))
//output: ()=>{handleButtonChange(false);}
If you are trying to add arguments to a handler in recompose, make sure that you're defining your arguments correctly in the handler. It is essentially a curried function, so you want to be sure to require the correct number of arguments. This page has a good example of using arguments with handlers.
Example (from the link):
withHandlers({
handleClick: props => (value1, value2) => event => {
console.log(event)
alert(value1 + ' was clicked!')
props.doSomething(value2)
},
})
for your child HOC and in the parent
class MyComponent extends Component {
static propTypes = {
handleClick: PropTypes.func,
}
render () {
const {handleClick} = this.props
return (
<div onClick={handleClick(value1, value2)} />
)
}
}
this avoids writing an anonymous function out of your handler to patch fix the problem with not supplying enough parameter names on your handler.
The problem is certainly the this binding while rending the button with onClick handler. The solution is to use arrow function while calling action handler while rendering. Like this:
onClick={ () => this.handleButtonChange(false) }
From react docs Passing arguments to event handlers
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
This same warning will be emitted on any state changes done in a render() call.
An example of a tricky to find case:
When rendering a multi-select GUI component based on state data, if state has nothing to display, a call to resetOptions() is considered state change for that component.
The obvious fix is to do resetOptions() in componentDidUpdate() instead of render().
I got the same error when I was calling
this.handleClick = this.handleClick.bind(this);
in my constructor when handleClick didn't exist
(I had erased it and had accidentally left the "this" binding statement in my constructor).
Solution = remove the "this" binding statement.
The onClick function must pass through a function that returns the handleButtonChange() method. Otherwise it will run automatically, ending up with the error/warning. Use the below to solve the issue.
onClick={() => this.handleButtonChange(false)}
The solution that I use to open Popover for components is reactstrap (React Bootstrap 4 components).
class Settings extends Component {
constructor(props) {
super(props);
this.state = {
popoversOpen: [] // array open popovers
}
}
// toggle my popovers
togglePopoverHelp = (selected) => (e) => {
const index = this.state.popoversOpen.indexOf(selected);
if (index < 0) {
this.state.popoversOpen.push(selected);
} else {
this.state.popoversOpen.splice(index, 1);
}
this.setState({ popoversOpen: [...this.state.popoversOpen] });
}
render() {
<div id="settings">
<button id="PopoverTimer" onClick={this.togglePopoverHelp(1)} className="btn btn-outline-danger" type="button">?</button>
<Popover placement="left" isOpen={this.state.popoversOpen.includes(1)} target="PopoverTimer" toggle={this.togglePopoverHelp(1)}>
<PopoverHeader>Header popover</PopoverHeader>
<PopoverBody>Description popover</PopoverBody>
</Popover>
<button id="popoverRefresh" onClick={this.togglePopoverHelp(2)} className="btn btn-outline-danger" type="button">?</button>
<Popover placement="left" isOpen={this.state.popoversOpen.includes(2)} target="popoverRefresh" toggle={this.togglePopoverHelp(2)}>
<PopoverHeader>Header popover 2</PopoverHeader>
<PopoverBody>Description popover2</PopoverBody>
</Popover>
</div>
}
}