this.setState inside the callback of this.setState not setting state - javascript

I have 2 list items which uses the same <ListItem /> component with this.props.a = true and this.props.b = true passed to it from a parent component (mentioning there are 2 because I am not sure that messes with anything but each of them should have their own independent component state) .
In the <ListItem /> component I have an _handleOnClick function. When the _handleOnClick function is fired, inspecting the component state shows a: false and b:true although I see got here logged in my console.
I would have expected this.setState({ b: false }) to be called in the callback and the state to change to a: false and b:false. What am I missing?
export default class ListItem extends PureComponent {
state = {
a: this.props.a, //this.props.a is true
b: this.props.b, //this.props.b is true
};
_handleOnClick = () => {
this.setState({ a: !this.state.a }, () => {
if (!this.state.a && this.state.b) {
console.log('got here')
this.setState({ b: false })
}
});
};
...
}

Why dont you try this
this.setState((prevState, props) => {
//add your stuff here by matching it with previous state
})

Why are you setting derived state from props? I believe it's considered an anti-pattern to store anything in a state that can be derived from props at any point in time.
also https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
That being said, I believe your first argument is supposed to be the full state object.
what-is-the-difference-between-passing-an-object-or-a-function-in-setstate

Related

State updates may be asynchronous, what is exactly this.props?

state = {
persons:[
{id:"cbhc", name:"surya",age:26,sex:"male"},
{id:"rgt", name:"sachin",age:36,sex:"male"},
{id:"cbcchc", name:"rahul",age:46,sex:"male"}
],
showdetails:false,
**counter:0**,
};
The above was the state of data in my application:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
To fix it, use a second form of setState() that accepts a function rather than an object. That function will receive the previous state as the first argument, and the props at the time the update is applied as the second argument:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
What was exactly here: props.increment ????
My piece of code:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
I want to know what is props.increment ??
my skeleton of component:
import React from "react";
//import mystyles from "./person.module.css";
//import Studentoutput from "./cockpit/cockpit";
const Singlestudent = props => {
console.log("child component skeleton rendering...");
return (
<div>
<p onClick={props.click}>{props.name}</p>
<p>{props.age}</p>
<p>{props.id}</p>
**<p>{props.increment}</p>**
<input type="text" onChange={props.update} value={props.name} />
</div>
);
};
export default Singlestudent;
since my state data is embedded inside with nested array and object, using map method to structure my skeleton comp data as below:
// import React from "react";
// //import mystyles from "./person.module.css";
// const Studentoutput = props => <input type="text" value={props.name} />;
// export default Studentoutput;
import React from "react";
import Singlestudent from "./student/singlestudent";
const Studentinfo = props => {
console.log("child component details rendering...");
return props.details.map((studentinfo, index) => {
return (
<Singlestudent
key={studentinfo.id}
name={studentinfo.name}
age={studentinfo.age}
**increment={props.increment}**
update={props.updated(studentinfo.id)}
click={props.clicked(studentinfo.id)}
/>
);
});
};
export default Studentinfo;
i passed increment={1} , hardcoded it.
now finally passing the above to my main parent which renders on browser
return (
<div className={mystyles.parent}>
<Studentinfo
details={this.state.details}
updated={this.updateStudentHandler}
clicked={this.deleteStudentHandler}
**increment={1}**
/>
</div>
);
from the above code snippet i'm changing my counter value through updateStudentHandler
updateStudentHandler = id => event => {
//debugger;
const studentIndex = this.state.details.findIndex(d => d.id === id);
console.log(studentIndex);
let duplicate = {
...this.state.details[studentIndex]
};
duplicate.name = event.target.value;
const dd = [...this.state.details];
dd[studentIndex] = duplicate;
this.setState({
details: dd
});
this.setState((referencetoprevState, Props) => {
return {
counter: referencetoprevState.counter + Props.increment
};
});
};
as soon as i change the text in input box, my counter should update but it returns NaN, why ????
refer to below screenshot attached
output of counter variable
but if i change the below code
this.setState((state, props) => {
return { counter: state.counter + props.increment };
});
with a value (9000) instead of props.increment results in updating the counter value as expected.
this.setState((state, props) => {
return { counter: state.counter + 9000 };
});
why i need to provide explicitly value not just like props.increment similar to state.counter because state.counter is taking its value as 0 from the previous state but props.increment not taking the value 1 from increment={1} from jsx of user defined component which is Studentinfo comp ??
Any limitations/reasons ??
As the React documentation states:
When React sees an element representing a user-defined component, it passes JSX attributes to this component as a single object. We call this object “props”.
I suggest to read further the official docs, especially Rendering a Component part.
Additionally setState() one here explains further:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.
In summary:
Basically you are using setState() function's updater argument in your code which takes two parameters, state and props:
state: is a reference to the component's state - mentioned above - at the time the change is being applied aka previous state.
props: current properties of the component.
You can think of like having the previous state and current properties of the component in two different arguments.
I hope this helps!
Yes i got , just like setting values to an object inside state, we provide values to props in the element tag which is jsx, so from my above code i figured out few things.
As per my understanding i believe that updater function for setState has two params, which basically sits in order as the first param would be previous state and the second is the current property of component.
Here my main component which renders on page with all other child components is .
so, props.increment is coming from Appl which extends Component from "react".
removing increment prop from my skeleton and body of skeleton i.e, from Singlestudent and Studentinfo comps
so finally :
this.setState((referencetoprevState, Props) => {
return {
counter: referencetoprevState.counter + Props.increment
};
});
would be:
this.setState((referencetoprevState, Props) => {
return {
counter: referencetoprevState.counter + 1
};
});

react - force componentDidUpdate() when props are same value

In my component im trying to sync the received props with the current state in order to make it visible from outside (I know this is an anti-pattern, but I haven't figured out another solution to this yet. Im very open to suggestions!).
Anyways, this is what I've got:
export class PopupContainer extends React.Component {
state = {
show: false,
};
shouldComponentUpdate(nextProps, nextState) {
if (this.props.show === nextProps.show) return true;
return true;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// if popup shown, fade it out in 500ms
if (this.props.show !== prevProps.show)
this.setState({ show: this.props.show });
if (this.state.show) {
setTimeout(() => this.setState({ show: false }), 2000);
}
}
render() {
return <Popup {...{ ...this.props, show: this.state.show }} />;
}
}
And in my external component I'm rendering the container :
<PopupContainer
show={this.state.popup.show}
message={this.state.popup.message}
level={this.state.popup.level}
/>
Now when I initially set this.state.show to true it works, but every successive assignment which is also true without any false assignment inbetween doesn't work. How do I force componentdidUpdate() to fire anyways even if the props are the same value? shouldComponentUpdate() didn't seem to solve the problem.
Thank you!
Edit: I noticed that the render() method is only called in the parent element. It seems like as there is no change in properties for the child, react doesn't even bother rerendering the childern which somehow makes sense. But how can I force them to rerender anyways?
This is kind of a hack, but it works for me.
In the child class
Add a property to state in constructor - let's call it myChildTrigger, and set it to an empty string:
this.state = {
...
myChildTrigger: ''
}
then add this to componentDidUpdate:
componentDidUpdate(prevProps) {
if(this.state.myChildTrigger !== this.props.myParentTrigger) {
// Do what you want here
this.setState({myChildTrigger: this.props.myParentTrigger});
}
}
In the parent class
Add a myParentTrigger to state in constructor:
this.state = {
...
myParentTrigger: ''
}
In the render method, add it as a prop, like this:
<ChildClass ... myParentTrigger={this.state.myParentTrigger} />
Now you can trigger a call to componentDidUpdate to execute whatever is inside the if-statement, just by setting myParentTrigger to a new value, like:
this.setState({ myParentTrigger: this.state.myParentTrigger + 'a' });

Higher Order Component not firing wrapper render in ReactJS

I'm using a high order component that is not rendering the child on a render change. The code below has been chopped down for simplicity sake.
The HOC that looks like this:
const withMyComponent = (WrapperComponent) => {
class HOC extends Component {
state = {
value: ''
};
changedHandler = (event) => {
this.setState({ value: event.target.value });
};
render() {
return (<WrapperComponent {...this.props}
changed={this.changedHandler}
value={this.state.value} />);
}
};
return HOC;
};
export default withMyComponent;
Then I have a child component that uses this HOC:
class myChildComponent extends Component {
render() {
return (
<input type="text"
onChange={(event) => this.props.changed(event)}
value={this.props.value || ''} />
);
};
};
export default withMyComponent(myChildComponent);
The problem I am experiencing is that the input is not updating with the new value that is passed back from the HOC. In fact, the child render is not even firing after the initial mount and doesn't seem to fire on any changed event.
I have placed debugging and console.logs in the HOC render and the changed event and the render are firing with the proper data.
So, if I type in the textbox, it fires the change event in the HOC and updates the state in the HOC and fires the render event in the HOC. But it is not firing the wrapped components render event, and thus the textbox never updates with any values.
Anyone able to solve this issue or lead me in the right direction?
Thanks again.
You need to pass an object to this.setState
const withMyComponent = WrapperComponent => {
class HOC extends Component {
state = {
value: ""
};
changedHandler = event => {
// Instead of
// this.setState( value: event.target.value );
// Do This.
this.setState({ value: event.target.value });
};
render() { ... }
}
return HOC;
};
Working Demo

How to not use setState inside render function in React

I have a complete running code, but it have a flaw. It is calling setState() from inside a render().
So, react throws the anti-pattern warning.
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
My logic is like this. In index.js parent component, i have code as below. The constructor() calls the graphs() with initial value, to display a graph. The user also have a form to specify the new value and submit the form. It runs the graphs() again with the new value and re-renders the graph.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
checkData: true,
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost !== nextProps.cost) {
this.setState({
checkData: true
});
}
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
if (this.props.cost.length && this.state.checkData) {
const tmp = this.props.cost;
//some calculations
....
....
this.setState({
theData: tmp,
checkData: false
});
}
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
<PieGraph theData={this.state.theData} />
</div>
);
}
}
The FormComponent is an ordinary form with input field and a submit button like below. It sends the callback function to the Parent component, which triggers the graphs() and also componentWillReceiveProps.
handleFormSubmit = (e) => {
this.props.onGpChange(this.state.value);
e.preventdefaults();
}
The code is all working fine. Is there a better way to do it ? Without doing setState in render() ?
Never do setState in render. The reason you are not supposed to do that because for every setState your component will re render so doing setState in render will lead to infinite loop, which is not recommended.
checkData boolean variable is not needed. You can directly compare previous cost and current cost in componentWillReceiveProps, if they are not equal then assign cost to theData using setState. Refer below updated solution.
Also start using shouldComponentUpdate menthod in all statefull components to avoid unnecessary re-renderings. This is one best pratice and recommended method in every statefull component.
import React, { Component } from 'react';
import FormComponent from './FormComponent';
import PieGraph from './PieGraph';
const initialval = '8998998998';
class Dist extends Component {
constructor() {
this.state = {
theData: ''
};
this.graphs(initialval);
}
componentWillReceiveProps(nextProps) {
if (this.props.cost != nextProps.cost) {
this.setState({
theData: this.props.cost
});
}
}
shouldComponentUpdate(nextProps, nextState){
if(nextProps.cost !== this.props.cost){
return true;
}
return false;
}
graphs(val) {
//Calls a redux action creator and goes through the redux process
this.props.init(val);
}
render() {
return (
<div>
<FormComponent onGpChange={recData => this.graphs(recData)} />
{this.state.theData !== "" && <PieGraph theData={this.state.theData} />}
</div>
);
}
}
PS:- The above solution is for version React v15.
You should not use componentWillReceiveProps because in most recent versions it's UNSAFE and it won't work well with async rendering coming for React.
There are other ways!
static getDerivedStateFromProps(props, state)
getDerivedStateFromProps is invoked right before calling the render
method, both on the initial mount and on subsequent updates. It should
return an object to update the state, or null to update nothing.
So in your case
...component code
static getDerivedStateFromProps(props,state) {
if (this.props.cost == nextProps.cost) {
// null means no update to state
return null;
}
// return object to update the state
return { theData: this.props.cost };
}
... rest of code
You can also use memoization but in your case it's up to you to decide.
The link has one example where you can achieve the same result with memoization and getDerivedStateFromProps
For example updating a list (searching) after a prop changed
You could go from this:
static getDerivedStateFromProps(props, state) {
// Re-run the filter whenever the list array or filter text change.
// Note we need to store prevPropsList and prevFilterText to detect changes.
if (
props.list !== state.prevPropsList ||
state.prevFilterText !== state.filterText
) {
return {
prevPropsList: props.list,
prevFilterText: state.filterText,
filteredList: props.list.filter(item => item.text.includes(state.filterText))
};
}
return null;
}
to this:
import memoize from "memoize-one";
class Example extends Component {
// State only needs to hold the current filter text value:
state = { filterText: "" };
// Re-run the filter whenever the list array or filter text changes:
filter = memoize(
(list, filterText) => list.filter(item => item.text.includes(filterText))
);
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
// Calculate the latest filtered list. If these arguments haven't changed
// since the last render, `memoize-one` will reuse the last return value.
const filteredList = this.filter(this.props.list, this.state.filterText);
return (
<Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
</Fragment>
);
}
}

setState not working for array reactjs

I have component Inbox and having checkbox in it... But It works on third click... does not work on first and second click... setState works perfect but it does't re-render after setState
class Inbox extends PureComponent {
constructor(props){
this.state = {
checked: [true]
}
}
updateCheck(i, convId) {
const state = this.state.checked
state[i] = !state[i]
this.setState(state)
}
render() {
return (
<input type="checkbox" checked={this.state.checked[i]} onClick={() => this.updateCheck(i, conv._id)}/>
)
}
}
You are not really updating the state correctly. setting state like
this.setState(state, () => {
console.log(this.state, '787878787878778787')
})
does not update the checked state using state but adds keys with array indices to state like
{0: true, 1: false, conversationId: '', checked: [true, false]};
You are instead mutating the checked state yourself using
state[i] = !state[i]
To update the state correctly, you would write
updateCheck(i, convId) {
const checked = [...this.state.checked]
checked[i] = !checked[i]
this.setState({ checked }, () => {
console.log(this.state, '787878787878778787')
})
}
The problem in your approach arises because you mutate the original state directly, subsequent setState calls may replace the original change and hence you see that behaviour.
According to documentation
Never mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
Working demo

Categories