Does React wait for render to update state? - javascript

I am checking the state of one of my keys twice within a function. However, even after operations are done on the key value it still reports the same value.
So I have a function called clickMe that will run from an onClick event for a button I have in the return for the component. When it's clicked the function should check to see the current state of totalRemaining before performing operations. If totalRemaining is greater than 0 it should uptick clicks and downtick totalRemaining. After that operation it should check to see if totalRemaining is still greater than 0. If it isn't it should disable the button. However, before and after the operation the value of totalRemaining is the same even though after render it is clear that it has been updated.
import React from 'react';
class Item extends React.Component{
constructor(props){
super(props);
this.state = {
clicks: 0,
totalRemaining: 10,
disabled: false,
}
}
clickMe(){
if(this.state.totalRemaining>0){
console.log(`Clicks: ${this.state.clicks}`)
console.log(`Total Remaining: ${this.state.totalRemaining}`)
this.setState({
clicks: this.state.clicks + 1,
totalRemaining: this.state.totalRemaining - 1
});
console.log(`Clicks: ${this.state.clicks}`)
console.log(`Total Remaining: ${this.state.totalRemaining}`)
if(this.state.totalRemaining===1){
this.setState({
disabled: true
})
}
}else{
this.setState({
disabled: true
})
}
}
render() {
return(
<div>
<button onClick={() => this.clickMe()} disabled={this.state.disabled}>Click Me {this.props.name}</button>
<input placeholder={this.state.totalRemaining}/>
<input placeholder={this.state.clicks}/>
</div>
)
}
}
export default Item;
Here is the console output:
this.state.totalRemaining
10
MyItem.js:18 Clicks: 0
MyItem.js:19 Total Remaining: 10
this.state.totalRemaining
10
this.state.totalRemaining
10
MyItem.js:25 Clicks: 0
MyItem.js:26 Total Remaining: 10

this.setState({
clicks: this.state.clicks + 1,
totalRemaining: this.state.totalRemaining - 1
},() => {this.state.totalRemaining < 1 ? this.setState({
disabled: true
}) : null});
reference: https://stackoverflow.com/a/54713679/11229002

Related

FCC Pomodoro Clock failing in-built tests

I'm doing FCC exercise Pomodoro Clock that sort of works. I think it’s the problem that when countdown was in it’s last minute, pause won’t work. Btw pause works if it’s not last minute. My suspicion is that this problem causes other tests to fail. Also test say that reset functionality doesn’t work, but it works.
Edit:
Deeper explanation of my problem:
Method paused() is used to change state paused from false to true and vice-versa. That method is called inside a timer() method whose job also is to initiate setInterval. setInterval is been set in variable this.clearTimer. If it’s paused true then clearInterval() is initiated and timer is stopped.
When element with start_stop id is clicked, paused changes, evaluation occurs and as i said it before; false => setInterval goes; true => setInterval stops.
When unpaused, setInterval from values recorded in state. Problem is that when you pause timer which below one minute(59, 58 etc. sec.), it won’t resume countdown despite state of paused being changed from true to false?! If it’s countdown above 1min., start/pause works as prescribed.
You can see the code at: my pen
Here is the code of main component(from local source):
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './style.scss';
import Item from "./item.js";
import Arrow from "./arrow.js";
import Fa from "./fa.js";
class Pomodoro extends React.Component {
constructor(props) {
super(props);
this.state = {
breakLength: 5,
multiplier: 25,
base: 1000,
time: 0,
minutes: 25,
seconds: 60,
paused: false,
session: false,
break: true,
disabled: false,
};
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
this.timer = this.timer.bind(this);
this.reset = this.reset.bind(this);
this.paused = this.paused.bind(this);
this.myRef = React.createRef();
this.clearTimer = null;
}
componentDidMount(){
this.setState({
time: ""+(this.state.multiplier*this.state.base*60),
minutes: this.state.multiplier,
seconds: this.state.seconds === 60 ? "00" : this.state.seconds ,
});
}
paused(){
this.setState({
paused: !this.state.paused
});
}
timer(){
this.paused();
if(this.state.paused === false){
if((this.state.minutes!==0 && this.state.seconds!==0) || (this.state.seconds!==0)){
this.clearTimer = setInterval(() => {
if(this.state.session===false){
console.log("Sada ide session.");
this.setState({
time: ""+(this.state.time-this.state.base),
minutes: this.state.minutes > 0 ? Math.floor(((this.state.time-this.state.base)/(this.state.base))/60) : this.state.minutes,
seconds: this.state.seconds > 0 ? this.state.seconds-1 : 59,
session: (this.state.minutes===0 && this.state.seconds===1) ? true : false,
break: (this.state.minutes===0 && this.state.seconds===1) ? false : true,
});
}
if(this.state.break===false && this.state.session===true && this.state.time==="0"){
console.log("Kraj session-a. Sada ide resetovanje, pa break.");
this.setState({
time: ""+(this.state.breakLength*this.state.base*60),
minutes: this.state.breakLength,
seconds: 60,
});
}
if(this.state.break===false){
console.log("Sada ide break.");
this.setState({
time: ""+(this.state.time-this.state.base),
minutes: this.state.minutes > 0 ? Math.floor(((this.state.time-this.state.base)/(this.state.base))/60) : this.state.minutes,
seconds: this.state.seconds > 0 ? this.state.seconds-1 : 59,
session: (this.state.minutes===0 && this.state.seconds===1) ? false : true,
break: (this.state.minutes===0 && this.state.seconds===1) ? true : false,
});
}
if(this.state.break===true && this.state.session===false && this.state.time==="0"){
console.log("Kraj break-a. Sada ide resetovanje, pa session.");
this.setState({
time: ""+(this.state.multiplier*this.state.base*60),
minutes: this.state.multiplier,
seconds: this.state.seconds === 60 ? "00" : this.state.seconds,
});
}
}, this.state.base);
}
}
else{
clearInterval(this.clearTimer);
}
}
reset(){
this.myRef.current.pause();
this.myRef.current.currentTime = 0;
clearInterval(this.clearTimer);
this.clearTimer = null;
this.setState({
breakLength: 5,
multiplier: 25,
base: 1000,
time: ""+(25*1000*60),
minutes: 25,
seconds: 60,
paused: false,
session: false,
break: true,
disabled: false,
});
}
increment(e){
console.log(e.target.id);
let myId = e.target.id;
if(myId==="break-increment"){
this.setState({
breakLength: this.state.breakLength <60 ? this.state.breakLength+1 : this.state.breakLength,
});
}
else if(myId==="session-increment"){
this.setState({
multiplier: this.state.multiplier < 60 ? this.state.multiplier+1 : this.state.multiplier,
time: this.state.time !== "60" ? ""+((this.state.multiplier+1)*this.state.base*60) : this.state.time,
minutes: this.state.minutes < 60 ? this.state.multiplier+1 : this.state.minutes,
});
}
}
decrement(e){
console.log(e.target.id);
let myId = e.target.id;
if(myId==="break-decrement" && this.state.breakLength > 1){
this.setState({
breakLength: this.state.breakLength > 1 ? this.state.breakLength-1 : this.state.breakLength,
});
}
else if(myId==="session-decrement" && this.state.multiplier > 1 && this.state.time > 1 && this.state.minutes > 1){
this.setState({
multiplier: this.state.multiplier > 1 ? this.state.multiplier-1 : this.state.multiplier,
time: this.state.time > 1 ? (""+((this.state.multiplier-1)*this.state.base*60)) : this.state.time,
minutes: this.state.minutes > 1 ? this.state.multiplier-1: this.state.minutes,
});
}
}
render(){
//console.log(this.state);
const minutes = (""+this.state.minutes).length===1 ? "0"+this.state.minutes : this.state.minutes;
const seconds = this.state.seconds===60 ? "00" : ((""+this.state.seconds).length===1 ? "0"+this.state.seconds : this.state.seconds);
const time = minutes+":"+seconds;
if(time==="00:00"){
console.log("1: "+time);
console.log("2: "+this.state.minutes+":"+this.state.seconds);
this.myRef.current.play();
}
/*if((this.state.minutes+":"+this.state.seconds)===time){
alert("alert2: "+this.state.minutes+":"+this.state.seconds);
}*/
const lastSesMin = (minutes==="00") ? {color: 'red',} : {};
const decrement = this.clearTimer ? ()=>{} : this.decrement;
const increment = this.clearTimer ? ()=>{} : this.increment;
const item2Head = <h3 id="break-label">Break Length</h3>;
const fa1 = <Fa klasa={"fa fa-arrow-down fa-2x"} id={"break-decrement"} onClick={decrement}/>;
const fa2 = <Fa klasa={"fa fa-arrow-up fa-2x"} id={"break-increment"} onClick={increment}/>;
const arr1 = [<Arrow klasa={"arrow"} key={0} arrow={item2Head}/>, <br key={1}/>, <Arrow klasa={"arrow"} key={2} arrow={fa1}/>, <Arrow id={"break-length"} klasa={"nums"} key={3} arrow={this.state.breakLength}/> , <Arrow key={4} klasa={"arrow"} arrow={fa2}/>];
const item3Head = <h3 id="session-label">Session Length</h3>;
const fa3 = <Fa klasa={"fa fa-arrow-down fa-2x"} id={"session-decrement"} onClick={decrement}/>;
const fa4 = <Fa klasa={"fa fa-arrow-up fa-2x"} id={"session-increment"} onClick={increment}/>;
const arr2 = [<Arrow klasa={"arrow"} key={0} arrow={item3Head}/>, <br key={1}/>, <Arrow klasa={"arrow"} key={2} arrow={fa3}/>, <Arrow klasa={"nums"} id={"session-length"} key={3} arrow={this.state.multiplier}/> , <Arrow key={4} klasa={"arrow"} arrow={fa4}/>];
const countdownLabel = (this.state.session===false && this.state.break===true) ? "Session" : "Break";
const item4Head = <h3 key={0} id={"timer-label"} style={lastSesMin}>{countdownLabel}</h3>;
const nums2 = <div key={1} className="nums" style={lastSesMin} id={"time-left"}>{time}</div>;
const arr3 = [item4Head, nums2];
const fa5 = <Fa key={0} klasa={"fa fa-play arrow controls"} title={"start-pause"}/>;
const fa6 = <Fa key={1} klasa={"fa fa-pause arrow controls"} title={"start-pause"}/>;
const fa7 = <Fa key={2} klasa={"fa fa-refresh arrow controls"} id="reset" title={"reset"} onClick={this.reset}/>;
const startPause = <div id="start_stop" key={4} onClick={this.timer}>{fa5}{fa6}</div>;
const arr4 = [startPause, fa7];
return(
<div className="grid-container cent">
<Item klasa={"item1"} arrowsAndNums={"Pomodoro Clock"}/>
<Item klasa={"item2"} arrowsAndNums={arr1}/>
<Item klasa={"item3"} arrowsAndNums={arr2}/>
<Item klasa={"item4"} arrowsAndNums={arr3}/>
<Item klasa={"item4"} arrowsAndNums={arr4}/>
<audio ref={this.myRef} id="beep" src="http://soundbible.com/grab.php?id=2158&type=wav"></audio>
</div>
);
}
}
ReactDOM.render(<Pomodoro/>, document.getElementById('root'));
Edit2:
I resolved pausing issue. I just changed line if(this.state.minutes!==0 && this.state.seconds!==0){ to line if((this.state.minutes!==0 && this.state.seconds!==0) || (this.state.seconds!==0)){.
Here are some images that show test errors even if there should be no errors.
PS: For error testing of the exercise it has been used fcc's error testing script, that is what generates these supposed errors.
Image1 of the errors:
Image2 of the errors:
Edit3:
And image of an error which might come out of testing suite:
Edit4:
As i recently discovered, even FCC's pomodoro fails tests sometimes. Sometimes in different browsers, sometimes on sundays, sometimes etc. ....
Long story short, i devised new code, that addresses issues in previous edits i made. Still, it suffers same problems as forementioned fcc's pomodoro. I read somewhere, something about timing events, whose implementation and execution depends on browsers and in this case test suite itself uses to, as also is my pomodoro. So there should be "conflicts" of sorts ... Resolving these inconsistencies and conflicts, so that in every browser, in every suit/test, looks insurmountable and i guess would ... What i'm trying to ask, is it ok if i submit my pomodoro, concerning everything being said?
My improved pomodoro
Added comments that explain functioning in the code so feel free to browse.
Image of my test fail:
On the upper left you can see 28/29 passed tests.
Notice: you can see updated code in codepen together with comments.
Just read some code (without testing your sandbox), but I found a problem that can lead to some obscure bugs due to possible race conditions:
paused(){
this.setState({
paused: !this.state.paused
});
}
timer(){
this.paused();
if(this.state.paused === false){
// ...
timer() is calling paused, where the state is updated. Then timer() checks the new state.
Problems:
setState may be batched, so consecutive calls to paused may be based on the some value.
Solution: use a function as state updater:
setState(prevState => ({
paused: !prevState.paused
}));
setState may be asynchronous (same link), so paused state may not reflect that change when being read in timer()!
Solution: use a callback as second parameter to setState():
setState(prevState => ({
paused: !prevState.paused
}), () => {
// You can read updated this.state.paused here....
});
So the whole code fragment could be implemented like this:
paused(callback){
setState(prevState => ({
paused: !prevState.paused
}), callback);
}
timer(){
this.paused(() => {
// if(this.state.paused === false){
// ...
});
But if you have many such places, you can quickly get in some kind of callback hell. And managing stopping/starting a timer in parallel to these state changes can become quite tricky by its own. You could at least simplify your timer but letting it always run (start in componentDidMount(), stop in componentWillUnmount()) and deciding on each tick what to do (e.g. doing nothing when being "paused", or letting a "pause" indicator blink ...)
Side note: variables like base do not need to be in state because they are just internal variables which don't need to trigger re-rendering of the component.

How to increment counter value for each dropdown selection

I am trying to increment the react state counter based on some option selection from the dropdown menu. The problem is I want to preserve the counter value and continuously increment based on condition like option "Test" is selected.
I am using below code to increment a counter if "Test" is selected in both the dropdown
{this.state.DownloadPrepare == "Test" ? this.state.Identify == "Test" : this.state.CalcNoOfObs+1}
You're probably looking for something like this: https://codesandbox.io/s/zealous-shirley-flznm
class App extends React.Component {
state = {
counter: 0,
listOne: null,
listTwo: null
};
incrementIfTest = event => {
console.log(event.target.name);
if (
//check if is already selected, do not want to double count
event.target.value === "Test" &&
this.state[event.target.name] !== "Test"
) {
//increment count
this.setState({
...this.state,
counter: this.state.counter + 1,
[event.target.name]: event.target.value
});
//decrease count if they remove Test selection, cannot be below 0.
} else if(this.state[event.target.name] === "Test"){
this.setState({
...this.state,
counter: this.state.counter > 0 ? this.state.counter - 1 : 0,
[event.target.name]: event.target.value
});
}
};
render() {
return (
<div>
<h4>{this.state.counter}</h4>
<select name="listOne" onChange={this.incrementIfTest}>
<option />
<option>DownloadPrepare</option>
<option>Test</option>
</select>
<select name="listTwo" onChange={this.incrementIfTest}>
<option />
<option>DownloadPrepare</option>
<option>Test</option>
</select>
</div>
);
}
}
Make use of the onChange() event-listener on the Dropdown (select tag). We can pass in the event and have access to the selected option through event.target.value. If it equals Test, we just increment the count
I made a script for on click you can do on the change as well I hope It's useful for you.
Thanks
constructor(props) {
super(props);
this.state = {
DownloadPrepare: "Test",
Identify: "",
CalcNoOfObs: 0
};
}
click() { // make your function what you want on change
this.state.DownloadPrepare == "Test"
? this.setState({
Identify: "Test"
})
: this.setState({
CalcNoOfObs: this.state.CalcNoOfObs + 1
});
}
<button onClick={() => this.click()}>click</button>

Jest: Unit test toHaveBeenCalled() not working

I have a small component in React which generates two random numbers on render, then asks the user to submit the sum of these numbers and if they are correct, increment their score.
The following code handles this game and works as intended in the browser:
import React, { Component } from 'react';
export const randomNumber = () => {
var maxNumber = 10;
var randomNumber = Math.floor((Math.random() * maxNumber) + 1);
return randomNumber;
}
class Arithmetic extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
numbers: {
x: randomNumber(),
y: randomNumber()
},
score: ''
}
this.updateVals = this.updateVals.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
updateVals() {
this.setState({
numbers: {
x: randomNumber(),
y: randomNumber()
},
score: this.state.score + 1
});
}
componentDidMount() {
this.setState({
score: 0
});
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
var isCorrect = this.state.numbers.x + this.state.numbers.y == this.state.value ? this.updateVals() : alert("Try again");
event.preventDefault();
}
render() {
return (
<section className="arithmetic">
<div className="arithmetic__game">
<div className="row arithmetic__row--details">
<div className="arithmetic__score">
Score: {this.state.score}
</div>
<div className="arithmetic__timer">
</div>
</div>
<div className="row arithmetic__row--main">
<div className="arithmetic__examples">
1 + 1 = 2<br/>
2 + 1 = 3<br />
</div>
<div className="arithmetic__game-container">
What is {this.state.numbers.x} + {this.state.numbers.y}?
<div className="arithmetic__form-container">
<form onSubmit={this.handleSubmit}>
<label>
Answer:
<input className="input-field" type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<button className="btn-submit" type="submit" onClick={(e) => (this.handleSubmit) ? this.handleSubmit(e) : null}>Submit</button>
</form>
</div>
</div>
</div>
</div>
</section>
);
}
};
However, when trying to check whether or not updateVals is called when the sum of both numbers is entered correctly, this fails. I have checked to see if handleSubmit is called on simulation of the "Submit" button being clicked, and it is called. I have also checked the values of the value and numbers props to see if the states have been correctly updated, which they have.
However, when updateVals is called, the score is incremented (and again, this is shown in the browser). But when I try to simulate this in Jest, the score remains at 0 as it is when it is initialised.
My test is as follows:
it("passes with correct input", () => {
const updateVals = jest.fn();
const handleSubmit = jest.fn();
Arithmetic.prototype.updateVals = updateVals;
Arithmetic.prototype.handleSubmit = handleSubmit;
let wrapper = mount(<Arithmetic />);
wrapper.find('.input-field').instance().value = wrapper.update().state().numbers.x + wrapper.update().state().numbers.y;
expect(wrapper.find('.input-field').instance().value).toEqual((wrapper.update().state().numbers.x + wrapper.update().state().numbers.y).toString());
wrapper.find('.input-field').simulate('change');
wrapper.find('.btn-submit').simulate('click');
console.log(wrapper.update().state().numbers.x, wrapper.update().state().numbers.y, wrapper.update().state().value);
expect(updateVals).toHaveBeenCalled();
});
Running tests in the terminal shows that, for instance, if the numbers.x is 1 and numbers.y is 9 then the value key in state is '10'. I'm not sure why when I test handleSubmit, it gets called and the test passes but updateVals does not.
I managed to get this working, I removed any events from the submit button and tested the form itself which already calls onSubmit and tested that the score itself updated in the state.
it("passes with correct input", () => {
const wrapper = shallow(<Arithmetic />);
const preventDefault = jest.fn();
const currentScore = wrapper.update().state().score;
wrapper.find('.input-field').simulate('change', {target: {value: wrapper.update().state().numbers.x + wrapper.update().state().numbers.y}});
wrapper.find('.main-form').simulate('submit', { preventDefault });
expect(wrapper.update().state().score).toEqual(currentScore+1);
});

Change color of selected page number (pagination)

I have created a simple React component with a table displaying 10 rows of data per page. The implementation of the pagination works as intended, but I want to be able to highlight the selected page.
In my constructor I have an initial state "active: null", which I then modify in a handleClick function.
constructor(props) {
super(props);
this.state = {
currentPage: 1,
eventsPerPage: 10,
active: null,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(index) {
this.setState({ currentPage: Number(index.target.id) });
if (this.state.active === index) {
this.setState({ active: null });
} else {
this.setState({ active: index });
}
}
And a function for setting the font-weight to bold.
myStyle(index) {
if (this.state.active === index) {
return 'bold';
}
return '';
}
Here is what I render.
const renderPageNumbers = pageNumbers.map((number, index) => (
<p
style={{ fontWeight: this.myStyle(index) }}
key={number}
id={number}
onClick={index => this.handleClick(index)}
>
{number}
</p>
));
return (
<div id="page-numbers">
{renderPageNumbers}
</div>
);
FYI: It's not the complete code.
I am not quite sure where I go wrong, any help is appreciated :)
pageNumbers.map((number, index) => index is zero based and am sure number starts from 1 so when index === 0 and number === 1, you would have this.myStyle(0) but
this.handleClick(0) will not work because you first #id === 1 not 0
why not just use number for your functions and remove index to safe confusion
You need to bind the context of the class to myStyle function in order to use this inside it.
So, in your constructor you should add it like this:
(see the last line)
constructor(props) {
super(props);
this.state = {
currentPage: 1,
eventsPerPage: 10,
active: null,
};
this.handleClick = this.handleClick.bind(this);
this.myStyle = this.myStyle.bind(this);
}

reactjs image slider navigation function, cant seem to get it working properly

i have a simple image slider with 3 images contained in an array and i just wish to navigate the slider with just 1 function that can go back and forth between images and if you click next on the last image it brings you to first image and vice versa, this is what i could make but its not working properly, instead it displays nothing if you click back on first image, and display nothing as well when you click next after last image
constructor() {
super()
this.state = {
imgArray: [ "/img/work1.jpg", "/img/work2.jpg", "/img/work3.jpg"],
imgNo: 0,
imgArrayLength: 2,
current: "/img/work1.jpg",
headlines: ["Pink Floyd Office", "Led Zeppelin Mania", "Central Perk Friends"],
headline : "Pink Floyd Office"
};
}
changeImg(n){
this.setState({
imgNo: this.state.imgNo += n
})
if(this.state.imgNo > this.state.imgArrayLength){
this.setState({
imgNo: 0
})
}
else if(this.state.imgNo < 0){
this.setState({
imgNo: this.state.imgArrayLength
})
}
this.setState({
current: this.state.imgArray[this.state.imgNo],
headline: this.state.headlines[this.state.imgNo]
})
}
<img src="/img/slider-left.png" class="slider-arrow" onClick={this.changeImg.bind(this, -1)} />
<img src="/img/slider-right.png" class="slider-arrow slider-right" onClick={this.changeImg.bind(this, 1)} />
You can calculate imgNo using - (imgArrayLength + imgNo + n) % imgArrayLength:
changeImg(n) {
const {
imgArrayLength, imgNo
} = this.state;
const current = (imgArrayLength + imgNo + n) % imgArrayLength;
this.setState({
imgNo: current,
current: this.state.imgArray[current],
headline: this.state.headlines[current]
})
}
I would also remove this props from the state because they are redundant:
current: this.state.imgArray[current],
headline: this.state.headlines[current]
In the render method use this.state.imgArray[this.state.imgNo].
from react documentation:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
So the first time you call:
this.setState({
imgNo: this.state.imgNo += n
})
this.state.imgNo didn't changed.
Changed your code to:
changeImg(n){
var imgNo; this.state.imgNo += n;
if(imgNo > this.state.imgArrayLength){
this.setState({
imgNo: 0
})
}
else if(imgNo < 0){
this.setState({
imgNo: this.state.imgArrayLength
})
}
this.setState({
current: this.state.imgArray[imgNo],
headline: this.state.headlines[imgNo]
})
}

Categories

Resources