In my increaseCount method I have 3 different ways of increasing the count. I thought the first method could be used twice but it doesn't work as it seems to merge the setState in the callback. What's the proper way to use a callback function and why does the arrow notation work? How is prevState.count being defined? It is never set to 0
import React from "react";
import { render } from "react-dom";
const styles = {
fontFamily: "sans-serif",
textAlign: "center"
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increaseCount() {
console.log(this);
this.setState({ count: this.state.count }, function() {
this.setState({ count: this.state.count + 1 });
});
this.setState({ count: this.state.count + 1 });
this.setState(prevState=>({ count: prevState.count + 1 }));
}
render() {
return (
<div style={styles}>
<div>
<button onClick={() => this.increaseCount()}>Increase</button>
</div>
<h2>{this.state.count}</h2>
</div>
);
}
}
render(<App />, document.getElementById("root"));
this.setState(prevState=>({count: prevState.count + 1}))
this.setState(function(prevState) {
return {count: prevState.count + 1};
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
this.setState(function (prevState) {
return {count: prevState.count + 1};
});
https://reactjs.org/docs/react-component.html#setstate
https://codepen.io/anon/pen/BrRzgB?editors=1011
Both prevState and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with prevState.
setState(updater[, callback])
Related
I have a child component which fires function pageChange when I press 2 button in the pagination bar:
import React from "react";
import "antd/dist/antd.css";
import { Pagination } from "antd";
const Child = props => {
function pageChange(pageNumber) {
props.pageChange(pageNumber);
console.log("Page child: ", pageNumber);
}
return (
<div>
<Pagination
defaultCurrent={1}
total={50}
onChange={pageChange}
/>
</div>
);
};
export default Child;
This child is used in the Parent class component:
import React, { Component } from "react";
import Child from "./Child";
class Parent extends Component {
state = {
pageIndex: 3
};
componentDidMount() {
this.getSetData(this.state.pageIndex);
}
getSetData(pageNumber) {
this.setState({
pageIndex: 5
});
console.log("Page state: ", this.state.pageIndex);
console.log("pageNumber: ", pageNumber);
}
pageChange(pageNumber) {
console.log("Page parent: ", pageNumber);
this.getSetData(pageNumber);
}
render() {
return (
<div className="container">
<Child pageChange={this.pageChange} />
</div>
);
}
}
export default Parent;
But I have troubles with it:
When I press 2 button I get an error this.getSetData is not a function. How to call getSetData from pageChange?
Statement this.setState({pageIndex: 9}); do not works during componentDidMount call. Why?
SOLVED
As far as I understand a function without an arrow has its own this. So one must use
pageChange = pageNumber => {
this.getSetData(pageNumber);
};
instead of
pageChange(pageNumber) {
this.getSetData(pageNumber);
}
setState is async, so to see change in console is useful to call from render
render() {
console.log("render ", this.state.pageIndex);
return (
<div className="container">
<Child pageChange={this.pageChange} />
</div>
);
}
An easy solution can be, using ES6 functions. So you don't need to bind that in constructor
getSetData = (pageNumber) => {
this.setState({
pageIndex: 5
});
console.log("Page state: ", this.state.pageIndex);
console.log("pageNumber: ", pageNumber);
}
EDIT 1
Try using your child component like this.
<Child pageChange={() => this.pageChange()} />
Running Example
EDIT 2
<Child pageChange={(e) => this.pageChange(e)} />
You didn't pass the parameter to pagechange function that's why it didn't recieve anything.
Try this it will surely work. I have tested this in your code.
May be you need to bind the function with this to use this.setState.
See here for more
class Parent extends Component {
constructor(props) {
super(props);
// binding here
this.state = {
pageIndex: 3
};
this.getSetData = this.getSetData.bind(this);
this.getSetDataArrow = this.getSetDataArrow.bind(this);
this.pageChange = this.pageChange.bind(this);
}
state = {
// items: [{ a: 1, b: 2 }, { a: 3, b: 4 }],
pageIndex: 3
};
componentDidMount() {
this.getSetDataArrow(this.state.pageIndex);
this.getSetData(this.state.pageIndex);
}
getSetData(pageNumber) {
this.setState({
pageIndex: 5
});
console.log("Page state: ", this.state.pageIndex);
console.log("pageNumber: ", pageNumber);
}
getSetDataArrow = pageNumber => {
this.setState({
pageIndex: 4
});
console.log("Page state: ", this.state.pageIndex);
console.log("pageNumber: ", pageNumber);
};
pageChange(pageNumber) {
console.log("Page parent: ", pageNumber);
// this.setState({
// pageIndex: 9
// });
// this.setState is not a function
this.getSetDataArrow(pageNumber);
this.getSetData(pageNumber);
// this.getSetData is not a function
}
render() {
return (
<div className="container">
<Child pageChange={this.pageChange} />
{/* <Child pageChange={this.pageChange} items={this.state.items} /> */}
{/* <Child2 pageChange={this.pageChange} /> */}
</div>
);
}
}
export default Parent;
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>
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.
I've been trying to get the countDown() function to run automatically inside render() function, but can't seem to figure it out. Here's the code:
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props) {
super(props);
this.countDown = this.countDown.bind(this);
this.state = {
count: 5,
message: ''
}
}
countDown() {
setInterval(() => {
if (this.state.count <= 0) {
clearInterval(this);
this.setState(() => {
return {message: "Click here to skip this ad"}
})
} else {
this.setState((prevState) => {
return {count: prevState.count - 1}
})
}
}, 1000)
}
render() {
return (
<div>
<h1 onLoad={this.countDown}>
{this.state.message ? this.state.message : this.state.count}
</h1>
</div>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('app'));
I'm not even sure if this is the optimal way to do it. My goal was to have a 5-second countdown displayed on screen then replace it with the download message/link when the countdown hits zero.
Use componentDidMount for starting the interval and clear it (to be sure) in componentWillUnmount too.
Then use the this.setState correctly
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 5,
message: ''
}
}
componentDidMount() {
this.inter = setInterval(() => {
if (this.state.count <= 0) {
clearInterval(this.inter);
this.setState({
message: 'Click here to skip this ad'
});
} else {
this.setState((prevState) => ({count: prevState.count - 1}));
}
}, 1000);
}
componentWillUnmount() {
clearInterval(this.inter);
}
render() {
return (
<div>
<h1>
{this.state.message ? this.state.message : this.state.count}
</h1>
</div>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('app'));
<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>
<div id="app"></div>
I would recommend calling countDown in componentDidMount also it is recommended to store the interval and clear it anyway in componentWillUnmount.
As is your countdown method will run indefinitely as you know is mostly the case with SetIntervals. Also try to avoid using onLoads to call event handlers. What you should do is make use of the component life cycle methods provided by React. Specifically ComponentDidMount() and ComponentDidUpdate() in your case.
For your countdown, try using something like this
class Clock extends React.Component {
state = {
counter: 10
}
//immediately is triggered. This only happens once. And we have it immediately call our countdown
componentDidMount() {
this.countDown()
}
countDown = () => {
this.setState((prevState) => {
return{
counter: prevState.counter - 1
}
})
}
//will get called everyt time we update the component state
componentDidUpdate(){
if(this.state.counter > 0){
setTimeout(this.countDown, 1000) //calls the function that updates our component state, thus creating a loop effect
}
}
render() {
return (
<div className="time">
The time is: {this.state.counter}
</div>
);
}
}
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>