setState is not updating state instantly [duplicate] - javascript

This question already has answers here:
Why calling setState method doesn't mutate the state immediately?
(3 answers)
Closed 4 years ago.
I have simple component
class App extends Component {
handleClick() {
let banana = {message: 'banana triggered'};
this.setState({banana});
console.log(this); // banana is set in state!!!!
console.log(this.state); // state is null :O
setTimeout(() => {
console.log(this.state); // banana is set!
}, 5)
}
render() {
const {state, actions} = this.props;
return (
<div>
{this.state && this.state.banana.message} <br />
<button onClick={() => this.handleClick()}>Test</button>
{state.alert.message && <p>{state.alert.message}</p>}
<p onClick={() => actions.alert.success("This is not")}>
This is magic
</p>
</div>
)
};
}
export default connect(
state => (
{
state: {...state}
}
),
dispatch => (
{
actions: {
dispatch: dispatch,
alert: {
success: text => dispatch(alert.success(text))
}
}
}
)
)(App);
problem is what i need to add this.state && in my JSX rendering to check if this.state exists at all, i understand what in JavaScript it's normal, but is not normal in React.js? Should he react to state change instantly? Also what get me confused, is what from two console.logs, first (this) have banana set in state, and second one is empty. How?
Image below:
p.s. there is no such problem with Redux, only local component state

react's docs mention that state updates are asynchronous.
In order to act based on the change of the state, react setState function provides a callback which you can use as follows:
this.setState({banana}, () => {
console.log(this.state);
});
In regards to your comment, the value of the state didn't actually exist when it was printed. the value was calculated only after you clicked the expand arrow in the console see this for more deatils

According to react docs, setState() is asynchronous, and multiple calls during the same cycle may be batched together.
If you check the updated state value, you can add a callback method
this.setState({ banana }, ()=> {
// console.log(this.state);
// Here's the updated state
});
In your case, the first console.log(this) doesn't set the banana. See your code in Sandbox. It looks like first two console logs don't show any state as the initial state is null and after the timeout when the asynchronous call has finished it set the state with banana.

Related

How to display react-redux state value without using setState?

This kind of problem has been answered before but I could not find a general way which works for all. Here is the state value screenshot:
The value came from node.js as
return res.send(available_balance);
Action was set as:
type: FETCH_DIGITAL_WALLET_BALANCE_BY_ID_SUCCESS,
digitalWalletUserAccountBalanceById: balance
As you can see the redux store has set the data right no doubt.
Now when I show the data as:
class UserUpdateModal extends React.Component {
constructor(props) {
...
}
render() {
return (
<div>
<h1>{this.props.initialValues.wallet_balance_by_id}</h1>
</div>
)
}
}
function mapStateToProps (state) {
return{
initialValues: {
wallet_balance_by_id: state.digitalWalletUserAccountBalanceById.data,
}
}
}
export default connect(
mapStateToProps,
)(withRouter(UserUpdateModal));
I get Error:
But when I create :
class UserUpdateModal extends React.Component{
this.state = {
wallet_balance_by_id:'',
}
this.getWallet_balance_by_id = this.getWallet_balance_by_id.bind(this);
}
getWallet_balance_by_id(){
this.state.viewWalletBalance==false?
this.setState({viewWalletBalance:true}):this.setState({viewWalletBalance:false})
}
....
}
Then call the function as input button it set the state and shows to the screen. So what is the basic way to shoe the redux state value to the screen without using a button to come around from the problem.
<h2>Balance</h2>
<h1>{ this.state.viewWalletBalance ?
this.props.initialValues.wallet_balance_by_id : null }</h1>
<input type="submit" value="Balance" onClick={this.getWallet_balance_by_id} />
{/* <h1>{this.props.initialValues.wallet_balance_by_id}</h1> */}
As you can see the redux store has set the data right no doubt.
The redux store gets the right value eventually but it doesn't always have the right value. Look again at the screenshot that you posted of Redux Dev Tools. Eventually the value is a number 10.12. But initially the value is an empty object {}. Why?
The problem is not in your component code or any of the code that you have included here. The problem is the initial state of your Redux store, which is setting the state.digitalWalletUserAccountBalanceById.data property to an empty object {}. Fix the initial state and your problems will go away. It should be a number or undefined.
From the screenshot, ( and mapStateToProps ) it seems like the initial value of wallet_balance_by_id is an Object ( which is state.digitalWalletUserAccountBalanceById.data ) which will throw that error because you can't render the Object like that, this is happening before the state update
{} => 10.12
It works when you click on the button because the value changes to a number and you can legally render it
update mapStateToProps to :
function mapStateToProps (state) {
return{
initialValues: {
wallet_balance_by_id: state.digitalWalletUserAccountBalanceById.data.wallet_balance_by_id,
// ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
// this should not be an object
}
}
}
Or better :
function mapStateToProps (state) {
return{
initialValues: state.digitalWalletUserAccountBalanceById.data
}
}

post setState evaluation of state

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.
})

Why my state does not change on click event with setState [duplicate]

This question already has answers here:
setState doesn't update the state immediately [duplicate]
(15 answers)
Closed 3 years ago.
I'm learning React, and actually don't understand why my state does not change immediately when I press a button, with a click event. I mean, I created a state "test: false", but when I click on my button, I just want to change the state, so I used setState. But the console still shows me the "false" when I click for the first time.
Here's a Codesandbox to illustrate my example.
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
test: false
};
this.display.bind(this);
}
display() {
this.setState({
test: true
});
console.log(this.state.test)
}
render() {
return (
<div className="App">
<button onClick={() => this.display()}>Click me!</button>
</div>
);
}
}
export default App;
I also want to show "Hello" if state is true and "Goodbye" if the state is false.
Can anyone help me, please?
I have checked your code and found you are doing mistake there.
As setState() is async. One easy way to handle that is to give it a callback.
To print the state just after the setState(), you need to use the code as follows:
display() {
this.setState({
test: true
}, console.log(this.state.test));
}
and about your updated query, you need to use ternary operator as follows:
render() {
return (
<div className="App">
<button onClick={() => this.display()}>Click me!</button>
{(this.state.test) ? <p>Hello</p> : <p>Bye</p>}
</div>
);
}
make display an asynchronous function and you can also use es6 for your function deceleration not compulsory though
const display = async _ => {
await this.setState({
test: !this.state.test
});
await console.log(this.state.test)
}
//now your button should appear like this
<button onClick={_ => display()}>Click me!</button>

Is this a valid way of updating state that depends on prevState?

I am following along with a video tutorial on using React. The presenter is currently detailing how to add a toggle button to a UI. They said to give it a go first before seeing how they do it, so I implemented it myself. My implementation was a little different to theirs, just the handler was different; but it does seem to work.
Can anyone with more experience using React tell me, is my toggleSideDrawerHandler wrong in some way? Or is it a valid shorter way of setting the state that depends on a previous state?
My implementation:
//Layout.js
class Layout extends Component {
state = {
showSideDrawer: false
};
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
closeSideDrawerHandler = () => {
this.setState({ showSideDrawer: false });
};
render() {
return (
<Fragment>
<Toolbar drawerToggleClicked={this.toggleSideDrawerHandler} />
<SideDrawer
open={this.state.showSideDrawer}
close={this.closeSideDrawerHandler}
/>
<main className={styles.Content}>{this.props.children}</main>
</Fragment>
);
}
}
//Toolbar.js
const toolbar = props => (
<header className={styles.Toolbar}>
<DrawerToggle clicked={props.drawerToggleClicked} />
<div className={styles.Logo}>
<Logo />
</div>
<nav className={styles.DesktopOnly}>
<NavItems />
</nav>
</header>
);
Tutorial implementation:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
Your solution works, but I guess in the part, where you call the toggleSideDrawerHandler you probably call it like
() => this.toggleSideDrawerHandler(this.state)
right?
If not, can you please paste the rest of your code (especially the calling part) to see where you get the prevState from?
This works, because you pass the old state to the method.
I would personally prefer the tutorials implementation, because it takes care of dependencies and the "user" (the dev using it) doesn't need to know anything about the expected data.
With the second implementation all you need to do is call the function and not think about getting and passing the old state to it.
Update after adding the rest of the code:
I think the reason, why it works is because the default value for your parameter is the one passed by the event by default, which is an event object.
If you use prevState.showSideDrawer you are calling an unknown element on this event object, that will be null.
Now if you use !prevState.showSideDrawer, you are actually defining it as !null (inverted null/false), which will be true.
This is why it probably works.
Maybe try to toggle your code twice, by showing and hiding it again.
Showing it will probably work, but hiding it again will not.
This is why the other code is correct.
You should stick to the tutorial implementation. There is no point in passing component state to the children and then from them back to the parents. Your state should be only in one place (in this case in Layout).
Child components should be only given access to the information they need which in this case is just showSideDrawer.
You are using this:
toggleSideDrawerHandler = prevState => {
let newState = !prevState.showSideDrawer;
this.setState({ showSideDrawer: newState });
};
This is a conventional way to update state in react, where we are defining the function and updating state inside. Though you are using term prevState but it doesn't holds any value of components states. When you call toggleSideDrawerHandler method you have to pass value and prevState will hold that value. The other case as tutorial is using:
toggleSideDrawerHandler = () => {
this.setState(prevState => {
return { showSideDrawer: !prevState.showSideDrawer };
});
};
This is called functional setStae way of updating state. In this function is used in setState methods first argument. So prevState will have a value equal to all the states in the component.Check the example below to understand the difference between two:
// Example stateless functional component
const SFC = props => (
<div>{props.label}</div>
);
// Example class component
class Thingy extends React.Component {
constructor() {
super();
this.state = {
temp: [],
};
}
componentDidMount(){
this.setState({temp: this.state.temp.concat('a')})
this.setState({temp: this.state.temp.concat('b')})
this.setState({temp: this.state.temp.concat('c')})
this.setState({temp: this.state.temp.concat('d')})
this.setState(prevState => ({temp: prevState.temp.concat('e')}))
this.setState(prevState => ({temp: prevState.temp.concat('f')}))
this.setState(prevState => ({temp: prevState.temp.concat('g')}))
}
render() {
const {title} = this.props;
const {temp} = this.state;
return (
<div>
<div>{title}</div>
<SFC label="I'm the SFC inside the Thingy" />
{ temp.map(value => ( <div>Concating {value}</div> )) }
</div>
);
}
}
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
So depending on requirement you will use one of the two ways to update the state.

Mutating nested object/arrays as states in React [duplicate]

This question already has answers here:
Whats the best way to update an object in an array in ReactJS?
(4 answers)
Closed 4 years ago.
I am using React components which look like this (a simplified version of the components I used, below).
My question is: how to make the same but using this.setState?
The below code works, but I am mutating the state directly, and I am receiving the following warning:
Do not mutate state directly. Use setState()
class App extends Component {
constructor(props) {
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
},{
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.state.playerState[index].hp = this.state.playerState[index].hp - 1
this.forceUpdate();
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
When rendered, it looks like this:
If you want to modify an existing value in the state, you should never get the value directly from the state and update the state object, but rather use the updater function in setState so you can guarantee the state values are the ones you need at the time of updating the state. This is just how React's state works and it's a very common React mistake.
From the official 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. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
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.
So you must get the value exactly when you want to update the component inside the setState function using the first argument of the updater function.
lowerPlayerHealth = (index) => () => {
// use setState rather than mutating the state directly
this.setState((state, props) => {
// state here is the current state. Use it to update the current value found in state and ensure that it will be set correctly
return (state); // update state ensuring the correct values
});
}
Solution
To lower a value found in state:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
}, {
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.setState((state, props) => {
state.playerState[index].hp -=1; //update the current value found in state
return (state); // update state ensuring the correct values
});
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
You've answered your own question: don't mutate state. Also, best practice suggests using the function version of setState.
Since playerState is an array, use Array.map to create a new array containing the same objects, replacing only the one you want to change:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: state.playerState.map(
(item, index) => index === indexToUpdate
? {
...item,
hp: item.hp - 1
}
: oldItem
)
}));
}
If you made playerState an object instead of an array, you can make it tidier:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: {
...state.playerState,
[indexToUpdate]: {
...state.playerState[indexToUpdate],
hp: state.playerState[idToindexToUpdatepdate].hp - 1
}
}
}));
}

Categories