ReactJS setState only works when nested inside setState - javascript

The issue:
When I use this.setState and I output the state in the callback, it doesn't change at all but when I nest the setstate inside a setstate it will then work correctly.
Example:
This doesn't work -
this.setState({
data: newData
});
This does work -
this.setState({
data: newData
}, () => {
this.setState({
data: newData
});
});
Does this have something to do with the way react batches state updates?
This is the actual code in which the setstate doesn't work unless I nest it (I've tried commenting everything out in this function and using setState to set coursePage to null but it doesn't work unless it's nested):
cancelCPIndexChange(index){
let temp = this.state.coursePages;
this.hideEditingCoursePage(index);
let canceledIndex = temp[index];
temp = temp.slice(0, index).concat(temp.slice(index+1));
temp = temp.slice(0, parseInt(canceledIndex.course_pageindex)-1).concat(canceledIndex).concat(temp.slice(parseInt(canceledIndex.course_pageindex)-1));
this.setState({
coursePages: temp
}, () => {this.setState({
coursePages: temp
});
});
}
This is another function on the same level as cancelCPIndexChanges that is able to modify the state of coursePages:
showEditingCoursePage(index){
let temp = this.state.coursePages;
temp[index].editingCoursePage = true;
this.setState({
coursePages: temp
});
}
These functions are in course.js. Both these functions are passed down to CoursePages.js and then to CoursePage.js.

According to: https://facebook.github.io/react/docs/component-api.html
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.
(I've noticed this myself in the past)
I'm not 100% certain, but I'd guess that the second setState in your callback function is "flushing" the pending state transition before creating the second one.
I'm not clear on where you want to consume the new value of state? It ought to be in the render method where you can be sure it's been updated (As the state transition triggers the render). If you want to use it immediately after the setState you still have the reference to the value, so can use that directly.

As said setState behaves asynchronously. There are two ways you can access the newly updated state value before render
Using a call back function for setState method
Use componentDidUpdate method. Using nextState param you can access the latest state. But make sure you should use setState method inside that

Related

Why does state not update as expected?

I am currently building a TicTacToe game and would like to store my current player in state as currentPlayer. After one player moves, I update currentPlayer to the opposite player. However, when I try to log the new state to the console, it's not producing the value of the updated state.
Here is my code:
state = {
currentPlayer: 'X',
}
// This function is triggered by an onClick attribute.
// It first finds the html element and renders the currentPlayer value from state.
// It then switchs the value of currentPlayer from X to O and calls setState to update the state.
// Why does the console.log not show the updated state value?
userDidMove = () => {
document.getElementById('cell').innerHTML = this.state.currentPlayer
let nextPlayer = this.state.currentPlayer === 'X' ? 'O' : 'X'
this.setState({
currentPlayer: nextPlayer,
})
console.log ('userDidMove changed state with ',this.state.currentPlayer)
}
Any help figuring out how to get this function to return the updated state value would be great!
State changes are asynchronous. When your new state is dependent on the previous state, use the state updater function instead.
When the state changes are committed you can use the callback which will have the updated state.
this.setState((previousState) => {
const nextPlayer = previousState.currentPlayer === 'X' ? 'O' : 'X';
return {
currentPlayer: nextPlayer
}
}, () => {
// your updated state related code here
console.log('userDidMove changed state with ', this.state.currentPlayer)
});
this.setState(updatedFunc, callback);
setState is asynchronous, so the state isn’t updated immediately. You can pass a callback as the second argument to setState that will only be called when state has been updated:
this.setState(
{ currentPlayer: nextPlayer },
() => console.log(`userDidMove changed state with ${this.state.currentPlayer}`)
);
setState (React Docs):
setState(updater[, callback]) OR setState(stateChange[, callback])
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
NOTE: I suggest observing state using the React Dev Tools, instead of logging it.
UPDATE: This answer initially stated, incorrectly, that setState returned a promise and suggested that you could chain .then() that would be called once state was updated. I've since corrected the answer, with inspiration from #Sushanth's answer.
State changes are asynchronous. so use a function instead and the second parameter of setState function you may call the callback function to console or something else to do.
this.setState(() => ({currentPlayer: nextPlayer}), () => {
console.log('state', this.state.currentPlayer);
})

React setState with function

I am attempting to setState in a React component using a function callback which is now the recommended way.
It is in the componentDidMount and when I get my jobs data back I need to update the state.
It works when I set it directly but I have attempted may functions callbacks and cannot get it to work.
Sample code provided below with one of my many attempts.
async componentDidMount(){
const jobs = await loadJobs();
this.setState({jobs});
//this.setState((prevState, jobs) => {return {jobs: [prevState,...jobs]}})
}
What is the correct syntax?
You only need to make use of functional setState when you want to update current state based on prevState, in your case it seems like you just want to set the state jobs, you would simply write
this.setState({jobs});
However if you want to use functional setState, you would still write
this.setState((prevState) => {
return {jobs};
})
You need to update the jobs by getting value from prevState and appending the result to the previous state, you would do it like
async componentDidMount(){
const jobs = await loadJobs();
this.setState((prevState) => {
return {jobs: [...prevState.jobs, ...jobs]}
})
}

Does render get called immediately after setState?

I have the following function:
onSelectDepartment = (evt) => {
const department = evt.target.value;
const course = null;
this.setState({ department, course });
this.props.onChange({ name: 'department', value: department });
this.props.onChange({ name: 'course', value: course });
if (department) this.fetch(department);
};
The question is, after the setState function get called, the render function on the component will be executed immediately or after function call is finished?
render function on the component will be executed immediately or after
function call is finished?
No one can take the guarantee of when that render function will get called, because setState is async, when we call setState means we ask react to update the ui with new state values (request to call the render method), but exactly at what time that will happen, we never know.
Have a look what react doc says about setState:
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
Check this answer for more details about async behavior of setState: Why calling setState method doesn't mutate the state immediately?
If you are looking execute some piece of code only once the setState is completed you can use below format.
this.setState({
flag: true
}, ()=> {
// any code you want to execute only after the newState has taken effect.
})
This is the way to make sure your desired piece of code only runs on the new state.

How to handle asynchronous calls to setState in React?

I have a method that toggles a boolean value in state by copying the value then updating state:
toggleSelected = () => {
let selected = this.state.lists.selected;
selected = !selected;
this.setState({
// update state
});
};
I have another method, fired by an onClick handler, that calls toggleSelected twice:
switchList = (listName) => {
const currList = this.getCurrentList();
if(listName === currList.name) return;
this.toggleSelected(listName);
this.toggleSelected(currList);
};
However it appears that the state doesn't finish getting set from the first call by the time the second call runs; if I set a timeout on the second call, it works fine.
What's the correct way to do this?
An alternative to what #SLaks suggested, useful sometimes, is using the setState(new_state, callback) method. The callback will be run once the state is updated and "visible".
In general, setState will not change the state immediately so that it is visible in this.state. From the docs
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
As noted in the React State/Lifecycle docs, the correct way to update state based on previous state is by passing a function into the setState() call, eg:
toggleSelected() {
this.setState((prevState, props) => {
return {
lists: {
selected : ! prevState.lists.selected
}
};
});
}
This is why you should always pass a lambda to setState(), so that it will give you the actual current state:
this.setState(state => ({ ..., selected: !state.lists.selected }))

Avoiding `setTimeout` 'hack' to ensure state is in sync

I wrote a small todo app using React, in which the user can add and remove items to their list.
As a bonus feature, the list is saved to localStorage as it's mutated by the user, and used on the initial mount of the component.
In both the addTodo and removeTodo methods, the pattern is this:
Set the new state by adding or removing an item of this.state.todos
Copy the state to localStorage
For example, here is the removeTodo method:
removeTodo(indexOfItemToRemove) {
const todosCopy = this.state.todos.slice();
todosCopy.splice(indexOfItemToRemove, 1);
this.setState({
todos: todosCopy
});
this.updateLocalStorageWithState();
}
Here is what I am currently doing to save this.state.todos to localStorage
updateLocalStorageWithState() {
setTimeout(() => {
localStorage.setItem('localStorageTodos', JSON.stringify(this.state.todos));
}, 1);
}
I found that without using setTimeout, the localStorage was always 1 step behind, using what the user might perceive as an outdated version of the list.
This feels like a hack. How can I incorporate a way of doing the same thing without having to use a setTimeout hack?
setState() is not necessarily executed synchronuosly. This causes the behavior you are experiencing:
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.
Source
Luckily, setState() takes a second parameter, which is a callback that is called when the state update has been completed. You can use that to synchronize your local storage entry:
this.setState({
todos: todosCopy
}, () => {
localStorage.setItem('localStorageTodos', JSON.stringify(this.state.todos));
});
Alternatively, why not set the local storage entry directly instead of reading from state first?
this.setState({
todos: todosCopy
});
localStorage.setItem('localStorageTodos', JSON.stringify(todosCopy));

Categories