I'm trying to build an app in React that will render interview questions from HTML, CSS, JS and React. I've set up a data.js file that is an array with objects inside that hold the Id, language, question and answer.
I've set up buttons for PreviousQuestion, NextQuestion and RandomQuestion.
I'm having trouble writing code for the PreviousQuestion, NextQuestion button. I've tried the following code on both but it doesn't work properly:
const questions = data.map((item) => item.question);
class App extends React.Component {
state = {
question: "",
count: 0,
};
prevQuestion = () => {
this.setState({
count: this.state.count - 1,
question: questions[this.state.count]
});
};
nextQuestion = () => {
this.setState({
count: this.state.count + 1,
question: questions[this.state.count]
});
I press nextQuestion this.state.count is 0 then 1 then 2 and then I press
PrevQuestion and this.state.count goes to 3 and only then back to 2 1 ...
Can anyone point me in the right direction on how to solve this so it always increments and decrements properly please?
The issue is that inside setState, your state has not changed yet. So in questions[this.state.count], this.state.count still reflects the old value, not the new one.
Therefore, this should fix your issue with prevQuestion:
prevQuestion = () => {
this.setState({
count: this.state.count - 1,
question: questions[this.state.count - 1]
});
};
And similarly for nextQuestion.
However, as Emile Bergeron stated in the comments, it would be better to only keep track of the count (i.e. the question index) in the state, and not the question itself. You can always access the relevant question using questions[this.state.count], saving you some headaches.
Use prevState in setState like the following one.
prevQuestion = () => {
this.setState(prevState => ({
count: prevState.count -1
question: questions[prevState.count -1]
}));
}
What's happening is that setState does not update the component state immediately, instead essentially queueing a state transition. See this answer. The answers / comments there also mention that you can use some component lifecycle methods such as componentDidUpdate to do something once a state transition has completed.
As others mentioned, you could update the question index by also adding / subtracting 1 from the state.count. Or, since I also don't think it's necessary to track the current question in the component's state, you could just refer to the question object in the render method:
const questions = data.map(item => item.question);
class Component extends Component {
constructor () {
super();
this.state = {
count: 0
}
}
nextQuestion () {
this.setState({ count: this.state.count + 1 });
}
previousQuestion () {
this.setState({ count: this.state.count - 1 });
}
render () {
// Get the question object based on the count in the component state
const question = questions[this.state.count];
return (
<!-- Stuff here -->
<p>{question}</p>
);
}
}
Related
I'm new to React, I was told by a book that it is incorrect to use setState method as the example below:
...
constructor(props) {
super(props);
this.state = {
counter: 0,
hasButtonBeenClicked: false
}
}
render() {
return (
<button onClick={ this.handleClick }>
Click
</button>
)
}
handleClick = () => {
this.setState({ counter: this.state.counter + 1 });
this.setState({ hasButtonBeenClicked: this.state.counter > 0 });
}
...
becuase React performs changes to state data asynchronously and may choose to group together several updates to improve performance.
I get the point, but since React performs changes asynchronously . there could be times and chances that the first setState method will get called first before the second setState method, it is just a matter of probability. But no matter how many times I tried, the second setState method always get called first, why?
Despite the fact that they're performed asynchronously, you're unlikely to see variance when testing from the same browser, and / or on the same hardware. Different browsers optimise instruction sets in slightly different ways, which is your best bet for examining performance (and why testing on all major desktop browsers is still recommended despite commercially Chrome often being "good enough").
The best way to do this - assuming you want them to fire in a specific order - is to chain the setState call as follows:
handleClick = () => {
this.setState({ counter: this.state.counter + 1 }, () => {
this.setState({ hasButtonBeenClicked: this.state.counter > 0 })
});
}
But I would recommend a different design regardless - tying the hasButtonBeenClicked logic to the counter is going to introduce problems down the line.
Don't reference this.state. Use callback variant of setState, get current state from there. That'll explicitly guard you against the "changed the state, but the state hasn't changed yet" situations.
And 2. Simply put those two updates into one run of setState:
this.setState((prevState) => ({
counter: prevState.counter + 1,
hasButtonBeenClicked: prevState.counter + 1 > 0 }));
If you have lots of states to update at once, group them all within the same setState:
Instead of:
this.setState({foo: "one"}, () => {
this.setState({bar: "two"});
});
Just do this:
this.setState({
foo: "one",
bar: "two"
});
Or in your case:
handleClick = () => {
this.setState({
counter: this.state.counter + 1,
hasButtonBeenClicked: this.state.counter > 0
});
}
You are right, React performs changes to state data asynchronously to improve performance.
In your case, you should write code like this:
handleClick = () => {
const counter = this.state.counter + 1;
this.setState({
counter: counter,
hasButtonBeenClicked: counter > 0
});
}
Or, you can use callback parameter of setState.
I am new to React and was learning how setState works. So, as you can see the code below:
class Counter extends React.Component {
constructor(props) {
super(props);
this.handleReset = this.handleReset.bind(this);
this.state = {
count: 5
};
}
handleReset() {
this.setState({count:0})
this.setState({count:this.state.count+1})
}
render() {
return (
<div>
<button onClick={this.handleReset}>reset</button>
</div>
);
}
So, what I expect from the above code is that when I click on button, instead of outputting 1 it adds 1 to the current state of count and shows 6. This is how I assume both setStates work in handleReset method. I do not know why it renders 6 instead of 1
As Rikin explained, setState batches them together and only executes the last one... And yes setState is async and does take a callback so if you wanted your expected result you'd do this instead:
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 5 };
this.handleReset = this.handleReset.bind(this);
}
handleReset() {
this.setState({ count: 0 },
/* here is you callback */
() => this.setState({ count: this.state.count + 1 }))
}
render() {
return (
<div>
<div>{this.state.count}</div>
<button onClick={this.handleReset}>reset</button>
</div>
);
}
}
Hope it helps... Here's a live demo: https://stackblitz.com/edit/react-dbc11s
setState calls are batched together thus only the final one makes the update. To explain that scenario let's use OP's example with some more similar hardcoded updates as:
handleReset() {
this.setState({ count: 0 })
this.setState({ count: 10 })
this.setState({ count: 100 })
this.setState({ count: this.state.count + 1 })
}
What happens above under the hood is you can think of it as:
Object.assign(
previousState, // { count: 5 }
{count: 0}, // { count: 0 }
{count: 10}, // { count: 10 }
{count: 100}, // { count: 100 }
{count: state.count + 1} // { count: 5 + 1}
)
Object.assign overwrites value of the count and thus only last value makes the final update which happens to be 6
In case if you want to use prior state's value, you would have to rely on functional setState method which uses existing state value at the time of execution and then performs update operation on it.
Both scenarios demo here: https://codesandbox.io/s/musing-rhodes-jsf6b
setState can take an updater function as an argument. In that case, the function will be passed the previous state, and you return a change object that will inform the next state.
The documentation even mentions this directly.
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 [in the documentation].
So instead of:
this.setState({ count: this.state.count + 1});
...you can do:
this.setState((prevState) => ({ count: prevState.count + 1 }))
For more information, see also this learn react article.
I think it might be silly question to ask but trust me I am beginner to reactJS . Could someone please explain me why we use prevState in ReactjS . I tried hard to understand but failed .
Here is my code. Please Help me to understand
state = {
placeName : '',
places : []
}
placeSubmitHanlder = () => {
if(this.state.placeName.trim()===''){
return;
}
this.setState(prevState => {
return {
places : prevState.places.concat(prevState.placeName)
};
});
};
prevState is a name that you have given to the argument passed to setState callback function. What it holds is the value of state before the setState was triggered by React; Since setState does batching, its sometimes important to know what the previous state was when you want to update the new state based on the previous state value.
So if multiple setState calls are updating the same state, batching setState calls may lead to incorrect state being set. Consider an example:
state = {
count: 0
}
updateCount = () => {
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
this.setState({ count: this.state.count + 1});
}
In the above code you might expect the value of count to be 4 but it would actually be 1 since the last call to setState will override any previous value during batching. A way to solve this to use functional setState:
updateCount = () => {
this.setState(prevstate => ({ count: prevstate.count + 1}));
this.setState(prevstate => ({ count: prevstate.count + 1}));
this.setState(prevstate => ({ count: prevstate.count + 1}));
this.setState(prevstate => ({ count: prevstate.count + 1}));
}
You use it when you want to override the current state with the last state's parameters.
From React docs :
According to the React docs "React may batch multiple setState() calls into a single update for performance. 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"
Link
Here is a demo with a commented-out code to give you more information: http://codepen.io/PiotrBerebecki/pen/rrGWjm
constructor() {
super();
this.state = {
value: 0
}
}
React docs: https://facebook.github.io/react/docs/reusable-components.html#es6-classes
The [React ES6 class] API is similar to React.createClass with the exception of getInitialState. Instead of providing a separate getInitialState method, you set up your own state property in the constructor.
Where does prevState come from?
The prevState comes from the setState api: https://facebook.github.io/react/docs/component-api.html#setstate
It's also possible to pass a function with the signature function(state, props). This can be useful in some cases when you want to enqueue an atomic update that consults the previous value of state+props before setting any values. For instance, suppose we wanted to increment a value in state:
this.setState(function(previousState, currentProps) {
return {
value: previousState.value + 1
};
});
This question already has answers here:
React setState not updating state
(11 answers)
Closed 4 years ago.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 1
}
}
componentDidMount() {
this.setState({
num: this.state.num+1
});
this.setState({
num: this.state.num+1
});
}
render() {
return (
<div>
{ this.state.num }
</div>
)
}
}
Calling setState twice in componentDidMount uses +1 to update num, but finally num is 2
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 1
}
}
componentDidMount() {
this.setState({
num: ++this.state.num
});
this.setState({
num: ++this.state.num
});
}
render() {
return (
<div>
{ this.state.num }
</div>
)
}
}
setState updates num using auto increment, but finally num is 3。
What is the difference between the two methods? And how to understand how setState updates the state?
thank you
setState is asynchronous, after it returns this.state hasn't changed yet:
this.setState({
num: this.state.num+1 // this.state.num is 1, so the value here is 2
});
this.setState({
num: this.state.num+1 // this.state.num is still 1, so the value is still 2
});
So that's the same as
this.setState({
num: 2
});
this.setState({
num: 2
});
With your second example however, you're mutating this.state.num so
this.setState({
num: ++this.state.num // this.state.num is 2 after increment
});
this.setState({
num: ++this.state.num // this.state.num is 3 after incrementing again
});
And this is effectively the same as
this.setState({
num: 2
});
this.setState({
num: 3
});
In general it is not a good idea to call setState and compute a new value based on this.state. If you want to change the state based on the current value in state, don't access this.state!
Instead, give a callback to setState:
setState(state => ({ num: state.num + 1 })
Consider this example from the ReactJS docs:
For example, if you
attempt to increment an item quantity more than once in the same
cycle, that will result in the equivalent of:
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
Subsequent calls will override values from previous calls in the same
cycle, so the quantity will only be incremented once. If the next
state depends on the current state, we recommend using the updater
function form, instead:
this.setState((state) => {
return {quantity: state.quantity + 1};
});
setState is asynchronous, so you can't depend on the current state while updating it they way you are.
please refer to this documentation, especially the part where a similar example is discussed :
This form of setState() is also asynchronous, and multiple calls during the same cycle may be batched together. For example, if you attempt to increment an item quantity more than once in the same cycle, that will result in the equivalent of: ...etc
I see similar questions in this field but my question is different from these as i don't set state directly from this.state .
my code is
state = {
count: 0};
handleIncrement = product => {
console.log(product);
this.setState({ count: ++this.state.count });
};
as you see i don't set state directly but given me this error at this line
When you do this:
++this.state.count
This tries to mutate the state directly, because you're trying to do this:
this.state.count = this.state.count + 1
which changes the current value of state, but you should do this using setState not directly as the error says.
Do this instead:
this.setState({ count: this.state.count + 1 });
As a best practice, use a callback to avoid a race condition, such as:
this.setState(prev => {
return {
count: prev.count + 1,
};
});
You shouldn't mutate the state directly you can do as follows.
this.setState(prevState => ({
count: prevState.count+1;
}));
You can functionally update the state, therefore it takes the previous value of state, the props you are passing it and calculates the next state like so:
handleIncrement = product => {
console.log(product);
this.setState((state, props) => {
return {
count: state.count + 1
}
});
};