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 }))
Related
I am using a hook component and several state variables. I have read about using useEffect() with params to get a kind of callback after updating a state. Example:
export const hookComponent = () => {
const [var, setVar] = useState(null);
useEffect(() => {
//do things
}, [var])
}
In this example, useEffect() would be executed on every setVar() call. In my case, I do not want to execute useEffect() everytime, but only on specific occasions.
I would like to give the setVar() some kind of information which I can use in useEffect() like setVar(newValue, true).
Note: I do not want to store this information in var.
Is there a way to do this?
Like Nizar said, simple conditional check on 'var' in useEffect
If expensive calc you can
const expensiveValue = useMemo(() => {
// other logic here if needed
// could even be simple return var=='x'?true:false, although this would be easier to do in the useEffect hook?
return computeExpensiveValue(var);
},[var]);
useEffect(() => {
//do things
//expensiveValue only changes when you want it to from the memo
}, [expensiveValue])
Thank you sambomartin and Nizar for your input.
For everyone looking for an answer:
After some further research I found 3 possible solutions:
Use a class component. If you really are dependent on that state update to be completed switch to a class component, which allows you to give the setState() a callback as a second param.
Use the useRef hook to determine where your state update is comming from. You can use this information in the useEffect() method.
Get independent from the state. I used this solution and externalized my callback function with the drawback of giving it every parameter on every call, although they are present in the component the states are saved.
As far as I know, the useEffect only triggers if the dependency value changes, not simply by executing setValue.
I offer you three solutions, the first one, close to what you want but without using useEffect hook, the second one is an extension of the first one, that may be required if you need control over the previous state, and the third, more general, like comments say, though it won't be triggered if the state is the same, even if you execute setValue.
First solution: Wrap your set value with another function that definitely controls what may happen after or before the new state:
export default function MyComponent() {
const [state, setState] = useState(null);
const handleChangeSetState = (nextState, flag) => {
if (flag) {
specialUseCaseCb();
}
setState(nextState);
};
return <div>{/* ... */}</div>;
}
Second solution: Wrap your set value with another function, like in the solution 1, and ask for the previous or next state within setState inner callback:
export default function MyComponent2() {
const [state, setState] = useState(0);
const handleChangeSetState = (increment, flag) =>
setState((prevState) => {
const nextState = prevState + increment;
// you may need prevState or nextState for checking your use case
if (flag) {
specialUseCaseCb();
}
return nextState;
});
return <div>{/* ... */}</div>;
}
Third solution: use useEffect hook to follow changes, remember though that setState won't re-trigger useEffect hook if the state is the same:
export default function MyComponent3() {
const [state, setState] = useState("");
// notice that this will only be triggered if state changes
useEffect(() => {
if (state !== "my-special-use-case") return;
specialUseCaseCb();
}, [state]);
return <div>{/* ... */}</div>;
}
Im making a function in react that get some information from Youtube API which I want it to get called just once when I refresh the page and put that information in a state. But when I use it in componentDidMount, it wont save the information and my state is still empty. here is the code:
constructor(props) {
super(props);
this.state = { vid: [] };
this.vidSearch = this.vidSearch.bind(this);
}
vidSearch = async () => {
const youtubeVid = await Youtube.get("/search");
this.setState({ vid: youtubeVid.data.items });
};
componentDidMount() {
this.vidSearch();
console.log(this.state.vid);
}```
setState may be asynchronous, so you won't be able to immediately check the state. But it has an optional callback which you can use that is called after the process has completed.
vidSearch = () => {
const youtubeVid = await Youtube.get("/search");
this.setState({ vid: youtubeVid.data.items }, () => {
console.log(this.state.vid);
});
};
componentDidMount() {
this.vidSearch();
}
According to the official documentation:
You may call setState() immediately in componentDidMount(). It will
trigger an extra rendering, but it will happen before the browser
updates the screen. This guarantees that even though the render() will
be called twice in this case, the user won’t see the intermediate
state. Use this pattern with caution because it often causes
performance issues. In most cases, you should be able to assign the
initial state in the constructor() instead. It can, however, be
necessary for cases like modals and tooltips when you need to measure
a DOM node before rendering something that depends on its size or
position.
In this case I would change my code to this:
constructor(props) {
this.state = {
vid: Youtube.get('/search').data.items
}
}
vidSearch is async and you are not awaiting the returned promise in componentDidMount before calling console.log.
All async functions wrap the return value in a promise, even implicit returns.
You can try this.vidSearch().then(() => console.log(this.state)).
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);
})
I am trying to change the state using setState and then call an action with new state but React dispatch the action right before the new state has been set. how do I wait for the new state and then dispatch the action ?
addressInput = e => {
this.setState({
address: e.target.value
});
this.props.filterSearch(this.state.address) //this will be called before state is set
}
this.setState provides a callback as a second parameter which is called after the state change has occurred. You should use the callback as follows
addressInput = e => {
this.setState({
address: e.target.value
},
this.props.filterSearch(this.state.address));
}
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. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
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