I understand that react doesn't update state immediately then can someone tell me how i can log this state synchronously as my state is not a boolean and this answer didn't help me setState doesn't update the state immediately. Also i don't understand why after clicking on prev button it increments the value first and then it decrements
class A extends Component {
constructor(props) {
super(props);
this.state = {
value: 1
}
}
handleNext() {
this.setState(prevState => ({
value: prevState.value + 1
}));
console.log(this.state.value);
}
handlePrev() {
if(this.state.value > 1) {
this.setState(prevState => ({
value: prevState.value - 1
}));
}
console.log(this.state.value);
}
render() {
<Button bsStyle="primary" id="prev" onClick={() => this.handlePrev()}>Prev</Button>
<Button bsStyle="primary" id="next" onClick={() => this.handleNext()}>Next</Button>
}
The second argument to setState is a callback that executes after the state has updated.
handleNext() {
this.setState({ value: this.state.value + 1 }, () => ({
console.log(this.state.value);
});
}
From the setState docs:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
setState takes a callback as its second argument.
handleNext() {
this.setState(prevState => ({
value: prevState.value + 1
}),() => console.log('updated', this.state.value));
}
You code is fine, try printing this.state.value in your render function.
Example:
class A extends Component {
constructor(props) {
super(props);
this.state = {
value: 1,
};
}
handleNext() {
this.setState(prevState => ({
value: prevState.value + 1,
}));
}
handlePrev() {
if (this.state.value > 1) {
this.setState(prevState => ({
value: prevState.value - 1,
}));
}
}
render() {
const { value } = this.state;
return (
<div>
<h2>{ value }</h2>
<button id="prev" onClick={() => this.handlePrev()}>Prev</button>
<button id="next" onClick={() => this.handleNext()}>Next</button>
</div>
);
}
}
It seems like your handlePrev is incrementing then decrementing because you're constantly printing the previous state. So when you decrement you are printing the result of the previous increment.
|---------------------|-------------------------|
| Current State | Your console.log |
|---------------------|-------------------------|
| 1 | | init
|---------------------|-------------------------|
| 2 | 1 | increment
|---------------------|-------------------------|
| 3 | 2 | increment
|---------------------|-------------------------|
| 2 | 3 | decrement
|---------------------|-------------------------|
Related
I was working on a slideshow component that changing its interval for auto-playing. When I click speed up or slow down, the state is using the value in one update before, not the one currently updated, even I used setState().
Edit:
For a detailed explanation of value not immediately updated and the neediness of using callback in setState(),
see this useful post When to use React setState callback
var id;
const data = [img1, img2, img3, img4];
class Slideshow extends React.Component {
constructor(props) {
super(props);
this.state = { ImgId: 0, interval: 2000 };
}
startSlideshow() {
this.pause(); // prevent start multiple setInterval()
id = setInterval(() => {
this.setState(state => ({...state, ImgId: (state.ImgId + 1) % data.length}))
}, this.state.interval);
console.log(this.state.interval);
}
pause() {
if (id) {
clearInterval(id);
}
}
slowDown() {
this.setState((state) => ({...state, interval: state.interval + 250}));
this.startSlideshow();
}
speedUp() {
this.setState((state) => ({...state, interval: state.interval === 250 ? 250 : state.interval - 250}));
this.startSlideshow();
}
render() {
return (
<>
<button onClick={() => this.startSlideshow()}>Start</button>
<button onClick={() => this.pause()}>Pause</button>
<button onClick={() => this.slowDown()}>Slow down</button>
<button onClick={() => this.speedUp()}>Speed up</button>
<img src={"images/"+data[this.state.ImgId].filename} className="w-100"/>
<h6 className="text-center">{data[this.state.ImgId].filename}</h6>
</>
);
}
}
Use like :
slowDown() {
this.setState((state) => ({...state, interval: state.interval + 250}), ()=>{
this.startSlideshow();
);
}
speedUp() {
this.setState((state) => ({...state, interval: state.interval === 250 ? 250 : state.interval - 250}), ()=>{
this.startSlideshow();
);
}
setState have a callback, trigger after setting complete
This question already has an answer here:
Why doesn't my arrow function return a value?
(1 answer)
Closed 3 years ago.
Following is my code in which I am trying to increment the count using click button but it's not updating the value. Though I am not getting any error in console as well. Let me know what I am doing wrong here.
JS Code -
class App1 extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
this.setCount = this.setCount.bind(this)
}
setCount() {
this.setState((state) => {
count: state.count + 1
})
}
render() {
return (
<>
<hr />
<h3>test increment</h3>
<button onClick={this.setCount}>Click</button>
<p>{this.state.count}</p>
</>
)
}
}
ReactDOM.render(<App1 />, document.getElementById('root'))
Codepen - https://codepen.io/anon/pen/LaMOEp
You are not returning anything. You could use return in side callback.
setCount() {
this.setState((state) => {
return {count: state.count + 1}
}))
}
Or you can use avoid using of return wrapping you return value in () after =>
setCount() {
this.setState((state) => ({
count: state.count + 1
}))
}
this.setState((state) => {
count: state.count + 1
})
In the above code, the curly brackets are the body of the function, count: is a line label, and state.count + 1 is an expression that never gets used. If you want to use the concise arrow function syntax to return an object literal, then you need to wrap the object in parentheses:
this.setState((state) => ({
count: state.count + 1
}))
The problem is in setCount(), where you miss a pair of parenthesis! Here's the correct version:
setCount() {
this.setState((state) => ({
count: state.count + 1
}));
}
There are two parenthesis more! One right after the => and one at then of the this.setState() call.
a react component that will display the current value of our counter.
The counter should start at 0.
There should be a button to add 1.
There should also be a button to subtract 1.
I am unable to understand the problem, as to what is it that I have missed or some wrong syntax.
const React = require('react');
class Counter extends React.Component{
constructor(...args){
super(...args);
this.state = { counter: 0 };
}
// Your event handlers
cincrement = () => {
this.setState({ counter: this.state.counter+1 });
};
cdecrement = () => {
this.setState({ counter: this.state.counter-1 });
};
render() {
return (
<div>
<h1>{this.state.counter}</h1>
<button type="button" onClick={this.cincrement}>
Decrement
</button>
<button type="button" onClick={this.cdecrement}>
Increment
</button>
</div>
)
}
}
The error that I get on running the code
/runner/node_modules/babel-core/lib/transformation/file/index.js:590
throw err;
^
SyntaxError: /home/codewarrior/index.js: Unexpected token (16:13) 14
| // Your event handlers 15 |
16 | cincrement = () => {
| ^ 17 | this.setState({ counter: this.state.counter+1 }); 18 | }; 19 |
at Parser.pp$5.raise (/runner/node_modules/babylon/lib/index.js:4454:13)
at Parser.pp.unexpected (/runner/node_modules/babylon/lib/index.js:1761:8)
at Parser.pp$1.parseClassProperty (/runner/node_modules/babylon/lib/index.js:2571:50)
at Parser.parseClassProperty (/runner/node_modules/babylon/lib/index.js:6157:20)
at Parser.pp$1.parseClassBody (/runner/node_modules/babylon/lib/index.js:2516:34)
at Parser.pp$1.parseClass (/runner/node_modules/babylon/lib/index.js:2406:8)
at Parser.pp$1.parseStatement (/runner/node_modules/babylon/lib/index.js:1843:19)
at Parser.parseStatement (/runner/node_modules/babylon/lib/index.js:5910:22)
at Parser.pp$1.parseBlockBody (/runner/node_modules/babylon/lib/index.js:2268:21)
at Parser.pp$1.parseBlock (/runner/node_modules/babylon/lib/index.js:2247:8)
It seems your babel config does not include class properties syntax
You could use normal prototype methods and then prebind them in constructor
Also since your next state depends on the prev state you should pass a callback to setState
const React = require('react');
class Counter extends React.Component{
constructor(...args){
super(...args);
this.state = { counter: 0 };
this.cincrement = this.cincrement.bind(this);
this.cdecrement= this.cdecrement.bind(this)
}
// Your event handlers
cincrement(){
this.setState(state => ({ counter: state.counter+1 }));
}
cdecrement() {
this.setState(state => ({ counter: state.counter-1 }));
}
render() {
return (
<div>
<h1>{this.state.counter}</h1>
<button type="button" onClick={this.cincrement}>
Decrement
</button>
<button type="button" onClick={this.cdecrement}>
Increment
</button>
</div>
)
}
}
React needs binding on event handlers.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
One way to avoid this is to define the handlers inside the constructor:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = ev => {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
Your original code, IIRC, needs a special babel plugin to work.
Consider the following code :
import React, { Component } from 'react';
class Counter extends Component {
state = { value: 5 };
increment = () => {
this.setState(prevState => ({
value: prevState.value + 1
}));
};
decrement = () => {
this.setState(prevState => ({
value: prevState.value - 1
}));
};
render() {
return (
<div>
{this.state.value}
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
)
}
}
How can I make it so that whenever I click the Decrement button, the value will not be less than 0. The value's minimum will always be zero and not -1, -2 ,-3 ,-4 ...
Just set a minimum value in your decrementing code:
decrement = () => {
this.setState(prevState => ({
value: Math.max(prevState.value - 1, 0)
}));
};
That's how number input works. To simplify the code you could try to use validity state (if your target browsers support it)
onChange(e) {
if (!e.target.validity.badInput) {
this.setState(Number(e.target.value))
}
}
Example
you can test it
const onPressDecrement = () => setCount((prevCount) => (Math.max(prevCount - 1,1)));
On Way Use Conditional (ternary) operator in decrement Function
decrement = () => {
this.setState(prevState => ({
value: prevState.value ? prevState.value -1 : 0
}));
};
this.setState(prevState => ({
score: prevState.score + 10,
rightAnswers: prevState.rightAnswers + 1,
currentQuestion: setTimeout(() => {
prevState.currentQuestion + 1
}, 2000)
}))
}
On button click I change the state. My goal is to have a delay in currentQuestion state change, during which I want to show certain status messages, yet I want to update the score right away without delays.
What's the proper way to do that?
PS: This variant doesn't work, it's for the overall representation of what I want to do.
Thanks.
You can do this multiple ways:
1) Make two calls to setState. React will batch any concurrent calls to setState into one batch update, so something like this is perfectly fine:
this.setState( prevState => ({
score: prevState.score + 10,
rightAnswers: prevState.rightAnswers + 1
}));
setTimeout( () => {
this.setState( prevState => ({
currentQuestion: prevState.currentQuestion + 1
}));
}, 2000);
2) You can use the setState callback to update state after your first call is finished:
this.setState(prevState => ({
score: prevState.score + 10,
rightAnswers: prevState.rightAnswers + 1
}), () => {
setTimeout( () => {
this.setState( prevState => ({
currentQuestion: prevState.currentQuestion + 1
}));
}, 2000);
});
First use setState to change score and question with some value like null so that you know its updating and then also set timeout after that.
class Example extends React.Component {
constructor(props) {
super(props)
this.state = {
score: 1,
question: "A"
}
}
update() {
this.setState(prev => ({
score: prev.score + 1,
question: null
}));
this.change = setTimeout(() => {
this.setState({question: "B"})
}, 2000)
}
render() {
let {score, question} = this.state;
let style = {border: "1px solid black"}
return (
<div style={style} onClick={this.update.bind(this)}>
<div>{score}</div>
<div>{question ? question : "Loading..."}</div>
</div>
)
}
}
ReactDOM.render( < Example / > , document.querySelector("#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"></div>