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>
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
I just started learning React and I'm trying to create a simple reaction time app. I got stuck a little and I don’t know how to solve it. I could solve it to change the className on click, but I'd like to add a function that runs only if the "game-area-off" is active and it should change the classname to "game-area-on" at random times between 3-6 seconds.
So far i have come up with the code:
import "./App.css";
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true, gameClass: "game-area-start" };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => ({
isToggleOn: !state.isToggleOn,
gameClass: state.isToggleOn ? "game-area-off" : "game-area-start",
}));
}
render() {
return (
<div className={this.state.gameClass} onClick={this.handleClick}>
<h1 className="default-text">
{this.state.isToggleOn ? "Click anywhere to start" : "Wait for green"}
</h1>
</div>
);
}
}
export default App;
In the handleClick callback you can use a timeout with a random delay to toggle the "game-area-on" state.
const randomDelay = Math.random() * 3000 + 3000;
setTimeout(() => {
this.setState({
// set go state
});
}, randomDelay);
I suggest using two pieces of state, 1 to track when the app is waiting to display the "on" value, and the second to display it. Additionally, the classname can be derived from the state, so it doesn't really belong there since it's easily computed in the render method.
Here is suggested code:
class ReactionTime extends React.Component {
state = {
gameRunning: false,
isToggleOn: false,
startTime: 0,
endTime: 0
};
// timeout refs
clearOutTimer = null;
gameStartTime = null;
componentWillUnmount() {
// clear any running timeouts when the component unmounts
clearTimeout(this.clearOutTimer);
clearTimeout(this.gameStartTime);
}
handleClick = () => {
if (this.state.gameRunning) {
// handle end click
if (this.state.isToggleOn) {
clearTimeout(this.clearOutTimer);
this.setState({
gameRunning: false,
isToggleOn: false,
endTime: performance.now()
});
}
} else {
// handle start click - reaction "game" started
this.setState((prevState) => ({
gameRunning: true,
isToggleOn: false,
startTime: 0,
endTime: 0
}));
// set timeout to display "on"
const randomDelay = Math.random() * 3000 + 3000;
setTimeout(() => {
this.setState({
isToggleOn: true,
startTime: performance.now()
});
}, randomDelay);
// reaction "game" timeout to reset if user is too slow
this.clearOutTimer = setTimeout(() => {
this.setState({
gameRunning: false,
isToggleOn: false,
startTime: 0,
endTime: 0
});
}, 10000); // 10 seconds
}
};
render() {
const { gameRunning, isToggleOn, startTime, endTime } = this.state;
const className = gameRunning
? isToggleOn
? "game-area-on"
: "game-area-off"
: "game-area-start";
const reactionTime = Number(endTime - startTime).toFixed(3);
return (
<div className={className} onClick={this.handleClick}>
<h1 className="default-text">
{gameRunning
? isToggleOn
? "Go!"
: "Wait for green"
: "Click anywhere to start"}
</h1>
{reactionTime > 0 && <div>Reaction Time: {reactionTime}ms</div>}
</div>
);
}
}
I have an object in my initial state, and when I press a button I want to increment that object's property + 1.
(it is a react native project)
My approach:
constructor(props) {
super(props);
this.state = {
myObject: {
incrementCount: 0, // When i press button it should be increment + 1
decrementCount: 0,
}
}
}
...
onPressButton = () => {
this.setState(
prevState => ({
myObject: {
...prevState.myObject,
incrementCount: this.state.myObject.incrementCount + 1,
},
}),
console.log('TOTAL incrementCount: ', this.state.myObject.incrementCount),
);
};
But when i press button, I get the following behavior:
console.log prints 0 for first click,
1 for second click.
Object update happen after console log. But I'm using that in setState callback.
Please pass a function as the callback instead of console log. From the docs the callback should be a function but you are executing console.log() instead of passing a callback.
setState(updater[, callback])
onPressButton = () => {
this.setState(
prevState => ({
myObject: {
...prevState.myObject,
incrementCount: prevState.myObject.incrementCount + 1
}
}),
() =>
console.log(
"TOTAL incrementCount: ",
this.state.myObject.incrementCount
)
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel">
class App extends React.Component {
constructor() {
super();
this.state = {
name: "React",
myObject: {
incrementCount: 0, // When i press button it should be increment + 1
decrementCount: 0
}
};
}
onPressButton = () => {
this.setState(
prevState => ({
myObject: {
...prevState.myObject,
incrementCount: prevState.myObject.incrementCount + 1
}
}),
() =>
console.log(
"TOTAL incrementCount: ",
this.state.myObject.incrementCount
)
);
};
render() {
return (
<div>
<p>{this.state.myObject.incrementCount}</p>
<button onClick={this.onPressButton}>Increment</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>
I'm fetching data (pics and descriptions) from server, and using them in infinite scroll. When I'm scroll to bottom of page I want to change state value (value of the page from which we collect data).
When I'm doing this, something goes wrong. State update correctly only on second and each next time.
Code:
class Gallery extends Component {
state = {
photos: [],
searchValue: "dog",
page: 1,
totalPages: null
};
getPhotos = () => {
const { photos, searchValue, page, totalPages } = this.state;
axios.get(`api_key=${API_KEY}&tags=${searchValue}&page=${page}`)
.then(res => {
this.setState({
photos: [...photos, ...res.data.photos.photo],
totalPages: res.data.photos.pages
});
if (page < totalPages) {
this.setState(prevState => ({ page: prevState.page + 1 }));
console.log(page); // returns 1 on the first call, but should return 2;
}
})
};
render() {
const { photos, page, totalPages, loading } = this.state;
return (
<div className={classes.Content}>
<InfiniteScroll
pageStart={page}
loadMore={this.getPhotos}
hasMore={page < totalPages}
loader={<Spinner />}
>
<Photos photos={photos} />
</InfiniteScroll>
</div>
);
}
}
and now, the problem is here:
if (page < totalPages) {
this.setState(prevState => ({ page: prevState.page + 1 }));
}
on the first function call, page is 1, but on my logic should be 2. On the next calls everything work good (page is 2, 3, 4 etc.). For now i'm loading two times the same photos. Can You help me?
As we know setState in react is asynchronous so you can’t see the value immediately when modified.
To get the updated value you should do something like below
getPhotos = () => {
const { photos, searchValue, page, totalPages } = this.state;
if (page < totalPages) {
this.setState(prevState => ({
page: prevState.page + 1
}), () => {
console.log(this.state.page);//will print 2
axios.get(`api_key=${API_KEY}&tags=${searchValue}&page=${this.state.page}`)
.then(res => {
this.setState({
photos: [...photos, ...res.data.photos.photo],
totalPages: res.data.photos.pages
});
})
});
}
}
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
}));
};