Decrementing a number in React State - javascript

I'm trying to decrement my timeLeft property on my state by one every second. In the startRecording function I call the startTimer method but can't seem to get the state to decrease. What am I doing wrong? Thanks!
class NimbusCamera extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
navigator: PropTypes.object.isRequired
}
state = {
camera: {
aspect: Camera.constants.Aspect.fill,
captureTarget: Camera.constants.CaptureTarget.disk,
type: Camera.constants.Type.front,
captureMode: Camera.constants.CaptureMode.video,
captureAudio: true,
flashMode: Camera.constants.FlashMode.auto
},
isRecording: false,
timeLeft: 30,
reachedLimit: false
}
startTimer = () => {
console.log('Starting timer...')
let timerId = setInterval(countdown, 1000); // Run function every second
const countdown = () => {
console.log('Counting down...')
if (this.state.timeLeft === 0) {
clearTimeout(timerId);
this.setState({isRecording: false})
} else {
console.log(Decrementing...)
this.setState({timeLeft: this.state.timeLeft - 1});
}
}
}
render() {
console.log(this.state)
return (
<View style={styles.container}>
<Camera
ref={(cam) => {
this.camera = cam;
}}
style={styles.preview}
aspect={this.state.camera.aspect}
type={this.state.camera.type}
captureAudio={this.state.camera.captureAudio}
flashMode={this.state.camera.flashMode}
>
<Text>{this.state.timeLeft}</Text>
<Text style={styles.capture} onPress={this.startRecording.bind(this)}>[CAPTURE]</Text>
<Text style={styles.capture} onPress={this.stopRecording.bind(this)}>[STOP]</Text>
</Camera>
</View>
);
}
startRecording = () => {
if (this.camera) {
this.camera.capture({mode: Camera.constants.CaptureMode.video})
.then((data) => this.props.dispatch(getPath(data.path)))
.catch(err => console.error(err));
this.startTimer();
this.setState({
isRecording: true
});
}
}
stopRecording = () => {
if (this.camera) {
this.camera.stopCapture();
this.setState({
isRecording: false
});
}
}
}

Incrementers like ++ and -- wont work. Anytime you want to update state based on a previous state value you should use this syntax.
this.setState((prevState) => ({ timeLeft: prevState.timeLeft - 1 });
Try putting your state object in a constructor:
constructor(props){
super(props);
this.state = {
camera: {
aspect: Camera.constants.Aspect.fill,
captureTarget: Camera.constants.CaptureTarget.disk,
type: Camera.constants.Type.front,
captureMode: Camera.constants.CaptureMode.video,
captureAudio: true,
flashMode: Camera.constants.FlashMode.auto
},
isRecording: false,
timeLeft: 30,
reachedLimit: false
}

Because -- executes after timeLeft in the object is assigned. It's not a reference, so the value setState gets is the one before decrementing.
var out = document.getElementById('out');
var outCorrect = document.getElementById('outCorrect');
function setState(newState) {
out.innerHTML = newState.timeLeft;
}
function setStateCorrect(newState) {
outCorrect.innerHTML = newState.timeLeft;
}
var timeLeft = 10;
setState({timeLeft: timeLeft--});
var timeLeftCorrect = 10;
setStateCorrect({timeLeft: timeLeftCorrect - 1});
<span id="out"></span>
<span id="outCorrect"></span>
Run the snippet and see why. a - b executes before the resulting value is assigned, a-- is executed after.

The simple way;
this.setState(state => {
return {
timeLeft: state.timeLeft - 1,
}
});

Related

Bug in my react Stopwatch code. Issue with clearInterval()?

I've been trying to build a simple stopwatch application in React. The app is rendering okay but the stop and clear buttons are not working. Once started the timer will just keep on running.
Been banging my head on the keyboard for awhile now with this. Any help appreciated.
Thanks.
class Stopwatch extends Component {
state = {
status: false,
runningTime: 0
};
handleClick = () => {
this.setState(state => {
if (state.status) {
clearInterval(this.timer);
} else {
const startTime = Date.now() - this.state.runningTime;
this.timer = setInterval(() => {
this.setState({ runningTime: Date.now() - startTime });
});
}
return { status: !state.status };
});
};
handleReset = () => {
clearInterval(this.timer);
this.setState({ runningTime: 0, status: false });
};
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
const { status, runningTime } = this.state;
return (
<div>
<p>{runningTime}ms</p>
<button onClick={this.handleClick}>{status ? 'Stop' : 'Start'}</button>
<button onClick={this.handleReset}>Reset</button>
</div>
);
}
}

Cannot set property 'myInterval' of undefined in React

Is anyone able to help me with this error that is appearing when the start timer function is called by a button component? I originally had it set up using componentDidMount so that the timer started on its own, but wanted to eventually implement it into a button click to start
Any other advice on how to clean up them if statements would be greatly appreciated
Cannot set property 'myInterval' of undefined
class Timer extends React.Component {
state = {
seconds: 5,
stage: "breathIn",
repetition: 4,
};
changeSeconds(x) {
const { y } = this.state;
this.setState(({ y }) => ({
seconds: x,
}));
}
changeStage(x) {
const { y } = this.state;
this.setState(({ y }) => ({
stage: x,
}));
}
changeRepetitions() {
const { repetition } = this.state;
this.setState(({ repetition }) => ({
repetition: repetition - 1,
}));
}
startTimer() {
this.myInterval = setInterval(() => {
const { seconds, stage, repetition } = this.state;
if (seconds > 0) {
this.setState(({ seconds }) => ({
seconds: seconds - 1,
}));
}
if (seconds === 0) {
if (repetition > 0) {
if (stage === "breathIn") {
this.changeSeconds(7);
this.changeStage("hold");
} else if (stage === "hold") {
this.changeSeconds(8);
this.changeStage("breathOut");
} else if (stage === "breathOut") {
if (repetition === 2) {
this.setState(({ repetition }) => ({
repetition: repetition - 1,
}));
this.changeSeconds(5);
this.changeStage("breathIn");
} else if (repetition === 1) {
this.changeSeconds(0);
this.changeStage("complete");
}
}
} else {
clearInterval(this.myInterval);
}
}
}, 1000);
}
componentWillUnmount() {
clearInterval(this.myInterval);
}
render() {
const { seconds, stage, repetition } = this.state;
return (
<div>
<Centerbutton startTimer={this.startTimer} />
<h1>{repetition}</h1>
<h1>{seconds}</h1>
</div>
);
}
}
export default Timer;
<Centerbutton startTimer={this.startTimer} />
should be either
<Centerbutton startTimer={this.startTimer.bind(this)} />
or
<Centerbutton startTimer={()=>this.startTimer()} />
It's a classic JS event binding problem
Just place your setInterval function inside componentDidMount life cycle method, follow the below example, you can't use the setInterval direct in react.
componentDidMount() {
this.interval = setInterval(() => {
// Your code here
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
Learn more about setInterval and Here is the demo app

React: how to add a spinner after click, and change screen after the animation completes

I saw there are already answered questions on how to add spinners during fetch requests.
However what I need is to stop showing the animation when the animation completes. The animation completes after the timeout is reached.
Also I have a best practice question.
It's a good practice to empty the resources on componentWillUnmount and clear there the timeout. In the code below I clear the timeout in a if condition, because it has to stop as the height of the element reaches the right level.
Is that ok as I did it? If now, how should it look like to have the same functionality in the componentWillUnmount lifecycle phase?
Here is the animation Component:
class Thermometer extends Component {
state = {
termFill : 0
};
componentDidMount() {
const interval = setInterval(() => {
this.setState({
termFill: this.state.termFill + 10
});
if (this.state.termFill === 110) {
window.clearInterval(interval);
}
}, 200)
}
render() {
const styles = {
height: `${this.state.termFill}px`
};
if (this.state.termFill < 100) {
return (
<section>
<div id="therm-fill" style={styles} />
[MORE CODE - SHORTENED FOR EASIER READING]
)
}
};
And here is the Component that has to appear after the animation disappears.
The steps are like this:
A user enter and uses this tool
The user clicks "calculate"
The animation appears instead or on top of the tool
When the animation completes, the animation Component disappears and the tool
is once again visible with its updated state (results of the
calculation).
class DiagnoseTool extends Component {
state = {
[OTHER STATES REMOVED TO KEEP THE CODE SHORTER]
wasBtnClicked: false
};
[OTHER RADIO AND CHECKBOX HANDLERS REMOVED TO KEEP THE CODE SHORTER]
onButtonClick = e => {
e.preventDefault();
this.calculate();
this.setState({
wasBtnClicked: true
})
};
addResult = () => {
const resultColor = {
backgroundColor: "orange"
};
let theResult;
if (this..... [CODE REMOVED TO HAVE THE CODE SHORTER]
return theResult;
};
calculate = () => {
let counter = 0;
let radiocounter = 0;
Object.keys(this.state).filter(el => ['cough', 'nodes', 'temperature', 'tonsillarex'].includes(el)).forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++
} else if (this.state.radioAge === "age45") {
radiocounter--
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
})
}
this.setState({
points: counter + radiocounter
});
};
render() {
const {cough, nodes, temperature, tonsillarex, radioAge, wasBtnClicked} = this.state;
return (
<Container>
<BackArrow />
[JSX REMOVED TO KEEP THE CODE SHORTER]
<div className="resultbox">
{
(wasBtnClicked) && this.addResult()
}
</div>
</div>
[HERE IS THE BUTTON]
<button
style={{height: "40px", width: "150px", cursor:"pointer"}}
type="submit"
className="calculateBtn"
onClick={this.onButtonClick}
disabled={!radioAge}
>CALCULATE</button>
</Container>
Add a boolean to your state and set it to false, when the user clicks the button set it to true, after doing the calculation set it to false.
calculate = () => {
let counter = 0;
let radiocounter = 0;
this.setState({
isLoading: true // set is loading to true and show the spinner
})
Object.keys(this.state)
.filter(el =>
["cough", "nodes", "temperature", "tonsillarex"].includes(el)
)
.forEach(key => {
// console.log(this.state[key]);
if (this.state[key] === true) {
counter += 1;
}
});
if (this.state.radioAge === "age14") {
radiocounter++;
} else if (this.state.radioAge === "age45") {
radiocounter--;
}
if (this.state.radioAge !== "") {
this.setState({
isDisabled: false
});
}
this.setState({
points: counter + radiocounter,
isLoading: false // set it to false and display the results of the calculation
});
};
Example
<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 {
timer = null;
constructor() {
super();
this.state = {
result: '',
isLoading: false
};
}
showContent = () => { this.setState({ isLoading: false, result: `7 + 5 = ${7 + 5}` })}
calculate = () => {
this.setState({
isLoading: true,
result: ''
});
this.timer = setTimeout(this.showContent, 5000);
}
componentWillUnmount = () => {
clearTimeout(this.timer);
}
render() {
return (
<div>
<p>7 + 5</p>
<p>{this.state.result}</p>
{ this.state.isLoading
? <p>Calculating...</p>
: <button onClick={this.calculate}>Calculate</button>
}
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
</script>

I need to compute two values in setState on pressing the equal button

I have built an application where two or more values are computed. My question is how I would be able to compute 5+5= 10 instead of the way it is being computed right now where each 5+5+=10 where I have to press the + or -button before I press the equals button?
import React, {Component} from 'react';
import Screen from './components/Screen'
import OperandButtons from './components/OperandButtons'
import CalcMemory from './components/CalcMemory'
class Calculate extends Component {
state = {
operator: '',
input: '',
current: '',
result: 0,
memory: [],
additionMarker: false,
subtractionMarker: false,
markingColor: 'green'
};
handleChange = (event) => {
this.setState({
input: event,
});
};
addition = () => {
this.setState({
operator: '+',
additionMarker: true,
subtractionMarker: false,
current: Number(this.state.input) + Number(this.state.current)
});
};
subtraction = () => {
this.setState({
current: this.state.input,
operator: '-',
subtractionMarker: true,
additionMarker: false,
});
this.setState(() => {
if (this.state.current > 0) {
let result = this.state.current - Number(this.state.input);
console.log(result);
return {
current: result,
}
}
})
};
clear = () => {
this.setState({
input: 0,
})
};
equals = () => {
this.setState({
result: this.state.current
})
};
savedResults = [];
addToMemory = () => {
this.savedResults.push(this.state.input);
};
useMemory = () => {
const toBeUsedInExpression = [...this.savedResults];
let latestNum = toBeUsedInExpression.pop();
this.setState({
input: latestNum
})
};
render() {
return <React.Fragment>
<Screen input={this.state.input} result={this.state.result} handleChange={this.handleChange}/>
<OperandButtons addition={this.addition}
subtraction={this.subtraction}
clear={this.clear}
equals={this.equals}
additionMarker={this.state.additionMarker}
subtractionMarker={this.state.subtractionMarker}
markingColor={this.state.markingColor}
/>
<CalcMemory addToMemory={this.addToMemory} useMemory={this.useMemory}/>
</React.Fragment>
}
}
export default Calculate;
expected 5+5=10
result 5+5=5.
Since "input" and "current" are numbers why don't initialize them with "0". Try "parseInt" function instead of "Number".

Prop getting lost in unusual situation

I am trying to pass props from my parent component to my child component. When I receive the props in componentWillReceiveProps(), one of the children of my coin prop gets lost. This can be seen with the console.log lines in the child component.
For some reason, coin.profit prints "undefined" while printing just the coin object shows that coin.profit indeed is in the coin object. I have looked over my code for hours now, and asked friends to look at it and to no avail. Any help would be much appreciated.
Child Component (https://github.com/kdelalic/cryptofolio/blob/master/src/js/progress.js):
class Progress extends Component {
constructor(props) {
super(props);
this.state = {
initial: 0,
profit: 0,
holdings: 0,
change: "0%"
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.coins !== this.props.coins) {
Object.keys(nextProps.coins).map((key) => {
const coin = nextProps.coins[key]
console.log(coin)
console.log(coin.price)
console.log(coin.profit)
this.setState({
initial: this.state.initial + coin.price * coin.amount,
profit: this.state.profit,
holdings: this.state.profit + this.state.holdings,
change: this.state.initial / this.state.profit * 100 + "%",
})
})
}
}
Parent Component (https://github.com/kdelalic/cryptofolio/blob/master/src/js/crypto.js):
class Crypto extends Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
}
getCurrentPrice = (key) => {
const { coins } = this.state;
var url = "https://min-api.cryptocompare.com/data/price?fsym=" + coins[key].value.substring(coins[key].value.indexOf("(")+1,coins[key].value.indexOf(")")).toUpperCase() + "&tsyms=" + coins[key].currency.toUpperCase();
axios.get(url)
.then(response => {
const price = response.data[coins[key].currency.toUpperCase()];
const profit = parseFloat((price - coins[key].price) * coins[key].amount).toFixed(2)
var newState = this.state;
newState.coins[key]["currentPrice"] = price;
newState.coins[key]["profit"] = profit;
this.setState(newState);
})
.catch(err => {
console.log(err)
});
};
checkPos = (num) => {
if (num > 0) {
return " positive"
} else if (num < 0) {
return " negative"
} else {
return ""
}
};
handleOpen = () => {
this.setState({ ...this.state, open: true });
};
handleClose = () => {
this.setState({ ...this.state, open: false });
};
coinData = (dataFromChild, key) => {
const newCoins = {
...this.state.coins
};
newCoins[key] = dataFromChild
this.setState({
...this.state,
coins: newCoins
}, () => {
this.getCurrentPrice(key);
this.setState({
...this.state,
})
this.handleClose();
})
};
render() {
const { coins } = this.state;
return (
<div className="crypto">
<Progress coins={this.state.coins}/>
In React, you should never mutate the existing state. In
var newState = this.state;
newState.coins[key]["currentPrice"] = price;
newState.coins[key]["profit"] = profit;
this.setState(newState);
you are never creating any new objects. You should be doing
this.setState({
...this.state,
coins: {
...this.state.coins,
[key]: {
...this.state.coins[key],
currentPrice: price,
profit,
},
},
});
to create new state objects for each item you are mutating.
Because you are modifying the existing object, means the object passed to componentWillReceiveProps will potentially be updated by your other code.

Categories

Resources