Ok, i'll try and make this quick because it SHOULD be an easy fix...
I've read a bunch of similar questions, and the answer seems to be quite obvious. Nothing I would ever have to look up in the first place! But... I am having an error that I cannot fathom how to fix or why its happening.
As follows:
class NightlifeTypes extends Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: event.target.checked});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
More code surrounds this but this is where my problem lies. Should work, right?
I've also tried this:
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: !this.state.barClubLounge});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
So I have those two console.log()'s, both should be the same. I'm literally setting the state to be the same as the event.target.checked in the line above it!
But it always returns the opposite of what it should.
Same goes for when I use !this.state.barClubLounge; If it starts false, on my first click it remains false, even though whether the checkbox is checked or not is based off of the state!!
It's a crazy paradox and I have no idea whats going on, please help!
Reason is setState is asynchronous, you can't expect the updated state value just after the setState, if you want to check the value use a callback method. Pass a method as callback that will be get executed after the setState complete its task.
Why setState is asynchronous ?
This is because setState alters the state and causes re rendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better UI experience and performance.
From Doc:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Using callback method with setState:
To check the updated state value just after the setState, use a callback method like this:
setState({ key: value }, () => {
console.log('updated state value', this.state.key)
})
Check this:
class NightlifeTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
}
handleOnChange = (event) => { // Arrow function binds `this`
let value = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: value}, () => { //here
console.log(value);
console.log(this.state.barClubLounge);
//both will print same value
});
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
}
ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function.
handleOnChange = (event) => {
let inputState = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: inputState}, () => { //here
console.log(this.state.barClubLounge);
//here you can call other functions which use this state
variable //
});
}
}
This is by-design due to performance considerations. setState in React is a function guaranteed to re-render Component, which is a costly CPU process. As such, its designers wanted to optimize by gathering multiple rendering actions into one, hence setState is asynchronous.
Related
I have a handle function which is called in another component. This passess a value called data, which I want to update into my state.
//basic code
initialState = {
wordCount:"Some Value"
text: "Some value"
}
class Home extends Component{
state = initialState;
handler(data){
console.log('Arrived',data)
this.setState({wordCount:data,text:GetText(data,this.state)});
this.validation();
}
validation(){
console.log(this.state.wordCount)
}
render(){
return (
<div><Component handlerFunc={this.handler.bind(this)}</div>
)
}
Printing out data in my handler function I do receive the right information as a string. The I use setState to replace wordCount's initial value with data. I do the same with text. GetText returns a string.
But when validation is run, my state is exactly the same. How could this be?
Note: setState in react is asynchronous.
This means there is no guarantee that state will have changed by the next statement. What you can do to avoid this is setState accepts callback which will be called when set state finishes.
this.setState(
{
wordCount:data,
text:GetText(data,this.state)
},
() => {
// Gets called when setState updates the state
this.validation()
});
In a react component, I update state in various ways, but I would like to do an evaluation (call a function) after the state was updated.
When I do the following, secondUpdate() does not access to the updated state, so it is one cycle late:
firstUpdate = e => {
this.setState({ email: e.state.value });
// ... some validation
secondUpdate();
}
secondUpdate() {
const allValid= this.state.aIsValid & this.state.bIsValid & this.state.cIsValid;
this.setState({ allIsValid: allValid });
}
How should I bind secondUpdate() to any or some state update?
You can use setState callback :
this.setState({ email: e.state.value } , () => {
// Will get called once the state is updated
// ... some valdidations
secondUpdate();
});
allIsValid shall not be a state at all. By having states that depend on each other, you risk that states get out of sync, and your logic breaks. Instead, as allIsValid is derived from other states, it can just be calculated based on the state inside render:
render() {
const { email } = this.state;
const allIsValid = email.length > 5 && /*...*/;
// ...
}
You can use componentDidUpdate().
componentDidUpdate(prevProps, prevState){
// code from secondUpdate()
}
Here is official doc
You might like to go through this:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
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:
this.setState({
// change the state
}, () => {
//Perform something after the state is updated.
})
Being new to react, I saw a similar example but they were not clearly explained and I didn't understand how to solve this problem. I have an API, the new data is posted to the API.
ComponentDidMount() will initiate the data from the API first time.
I went through the documentation and saw that componentDiUpdate() will always re-render the page if the new data is added.
This is my code so far:
constructor(props){
super(props);
this.state = {
newData: [],
dataApi: this.props.getAllData() //method that is using GET_ALL_DATA actions/reducers using fetch(get)
}
}
// it gets the data
componentDidMount() {
this.state.dataApi
}
componentDidUpdate(prevProps, prevState){
this.state.dataApi.then(data => {
if(prevProps.data != this.props.data) {
this.setState({newData: data});
}
}
}
ComponentDidUpdate() errors:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
How can I fix this problem? by getting the new data from the API when someone makes a post request? Thanks
Solved the problem, thanks everyone:
Solution to the problem will be:
this.state = {
allData: [],
isSubmitted: false
}
async componentDidUpdate(prevProps, prevState){
if(this.mounted && this.state.isSubmitted){
const allData = await this.props.getAllData();
this.setState({isSubmitted: false,
allData: allData.data })
}
componentDidMount(){
this.mounted=true
}
componentWillUnmount() {
this.mounted = false;
}
functionAddDataToApi(){
// some logic
this.setState({isSubmitted: true})
}
The componentDidMount() lifecycle method gets called once only when your component is rendered for the first time.
In your constructor(), you don't need to put a promise on your state. Instead you can remove dataApi and just call the method directly. In componentDidMount(), you'll make your API call. When the API call has finished, you can use this.setState().
The componentDidUpdate method gets called every time one of your prop or state values gets updated. As such, it is a bad idea to update state within it as you risk infinitely looping.
constructor(props){
super(props);
this.state = {
newData: [],
};
}
// it gets the data
componentDidMount() {
this.props.getAllData().then(data => {
this.setState({
newData: data,
});
});
}
if you want to hook to props change before re-render try hooking into componentWillReceiveProps. componentWillReceiveProps will update state synchronously.
also.. code in your componentDidMount will do nothing :)
// it gets the data
componentDidMount(){
this.state.dataApi}
try to replace it with
// it gets the data
componentDidMount() {
this.state.dataApi.then(data => {
this.setState({
newData: data,
});
});
}
When setting the state asynchronously (for example, after a promise is resolved), it's important to make sure that your component is still mounted, otherwise, you risk setting the state on an unmounted component (which triggers the error that you are getting).
To do that, you will need to set some kind of a flag (like this.mounted in the below example):
componentDidMount() {
this.mounted = true;
}
async componentDidUpdate() {
const data = await someAPICall();
if (this.mounted && !_.isEqual(this.state.data, data)) { // See comment below
this.setState({data});
}
}
componentWillUnmount() {
this.mounted = false;
}
Also, when comparing the old data with the new data, you'll need to perform a deep compare (in the example above I'm using lodash isEqual(...)).
A shallow compare (i.e. this.state.data !== data) compares the references of each of the objects, which are always going to be different, regardless of the actual data, and therefore will run into an infinite loop because setState() will trigger another componentDidUpdate() and so on.
constructor(props) {
super(props);
this.state = {
active: false,
showSideBar: false,
className: ""
}
}
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
});
alert(this.state.className);
}
else if (this.props.SideBarWithIcon) {
this.setState({
className: "wrapper_clopsed"
});
}
}
I am updating my state with the help of the props but the component is getting props but state is not updating
setState is asynchronous. Just alert in a callback to the method instead.
if (this.props.overlay) {
this.setState(
{ className: "wrapper_overlay" },
() => alert(this.state.className);
);
}
Note: you can, and should, also use shouldComponentUpdate to check for when a setState call completes
Since setstate is async in nature so you maynot see updated state in alert.
You can use that in callback which will be called once the setstate is done. like this
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
}, ()=> {alert(this.state.className);});
}
State updates may be asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
// Wrong
this.setState({
className: "wrapper_overlay"
});
To fix it, use a second form of setState() that accepts a function
rather than an object.
// Correct
this.setState((state, props) => ({
className: "wrapper_overlay"
}));
or just
this.setState(() => ({
className: "wrapper_overlay"
}));
In React.js, running program thread does not wait for setting state and continues its execution, until and unless operation defined in setState() callback.
Your state is getting set but as your alert() is after setstate i.e. not in callback of setState() that's why its getting previous value because at the time state is setting new value, thread is not waiting but executing the next instruction which is alert().
if (this.props.overlay) {
this.setState({ className: "wrapper_overlay" }, () => alert(this.state.className););
}
Hope this works for you.
Just use the callback method to update the state, as
setState() might work asynchronously.
this.setState(() => {
className: "wrapper_clopsed"
});
I have an asynchronous function like so:
componentDidMount() {
someAsyncFunction().then((data) => {
this.setState({ something: data });
});
}
If go back to the previous screen, I will get the following error:
Warning: Can't call setState (or forceUpdate) on an unmounted component.
Is there something I can do to cancel this setState() if I go back to a previous screen while the async Is still running?
You can use this workaround:
componentDidMount() {
this._ismounted = true;
someAsyncFunction().then((data) => {
if (this._ismounted) {
this.setState({ something: data });
}
});
}
componentWillUnmount() {
this._ismounted = false;
}
This way the sesState will be called only if the component is mounted.
But this (as suggested in the comments) is an antiPattern and is to be used only when there is no another way to cancel the asyncFunction instead of waiting for it to be solved and then make the check.
First, you should not use isMounted() to wrap your code in an if-Statement.
(https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html)
In your case I think you have several choices: You could fire an action in your asynchronous function which sets the redux state instead of the components state.
Or if you really need it, you could set a flag in your componentDidMount and set it on false in componentWillUnmount.
constructor(props){
super(props);
this.state={
mounted: true
}
}
componentDidMount() {
this.setState({mounted: true});
someAsyncFunction().then((data) => {
if(this.state.mounted) this.setState({ something: data });
});
}
componentWillUnmount() {
this.setState({mounted: false});
}