Cannot set property 'myInterval' of undefined in React - javascript

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

Related

how to stop a timer in setInterval by click then resume by click again?

I am new to react, I am trying to write a react component, component has several features.
user can input a random number, then number will be displayed in the
page too.
implement a button with text value 'start', once click the button,
the number value displayed will reduce one every 1second and the
text value will become 'stop'.
continue click button, minus one will stop and text value of button
will become back to 'start'.
when number subtract to 0 will automatically stop itself.
I have implemented the first and second feature. but when I try to click stop to stop number from reducing 1, it does not work.
I am wondering since I used type=true/false to indicate the state of type is start or stop. Because in the start state, number should automatically reduce 1. And on the stop state, reducing 1 should stop. So, the timer function should accurate according to the state of type.
Also I am not sure if I used clearInterval method right.
I really appreciate if someone could give me a hand.
code is here:
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [{ id: 1, number: "" }],
type: false
};
this.handleClick = this.handleClick.bind(this);
}
changeNumber = (e, target) => {
this.setState({
details: this.state.details.map(detail => {
if (detail.id === target.id) {
detail.number = e.target.value;
}
return detail;
})
});
};
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
var myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};
render() {
return (
<div>
{this.state.details.map(detail => {
return (
<div key={detail.id}>
Number:{detail.number}
<input
type="number"
onChange={e => this.changeNumber(e, detail)}
value={detail.number}
/>
<input
type="button"
onClick={() => this.handleClick()}
value={this.state.type ? "stop" : "start"}
/>
</div>
);
})}
</div>
);
}
}
export default App;
You need to declare var myTimer outside of the handleClick() function.
So it's something like:
var myTimer;
...
handleClick = () => {
this.setState(prevState => ({
type: !prevState.type
}));
if (this.state.type === false) {
myTimer = setInterval(
() =>
this.setState({
details: this.state.details.map(detail => {
if (detail.id) {
detail.number = parseInt(detail.number) - 1;
}
return detail;
})
}),
1000
);
} else if (this.state.type === true) {
clearInterval(myTimer);
}
};

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>

In React, how should I pass a starting elapsed time to a stopwatch component and start a running incrementer from that elapsed time?

I can get a static elapsed seconds to display based on the pulled start time but I can't get the elapsed seconds to then continue counting. I know I probably shouldn't be using the props to update a state in the child but even without that I couldn't get it to work and tried that as a workaround. Any help is appreciated.
Parent looks like this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
elapsedSeconds: -1
};
this.changeElapsedSecondsParent = this.changeElapsedSecondsParent.bind(this);
}
changeElapsedSecondsParent(newElapsed) {
this.setState({
elapsedSeconds: newElapsed
});
}
render() {
const { elapsedSeconds } = this.state;
return (
<div>
<Stopwatch
elapsedSeconds={elapsedSeconds}
changeElapsed={this.changeElapsedSecondsParent}
/>
</div>
);
}
}
Child stopwatch looks like this:
const formattedSeconds = (sec) =>
~~(sec / 60) +
':' +
('0' + sec % 60).slice(-2)
export class Stopwatch extends React.Component {
constructor(props) {
super(props);
this.state = {
secondsElapsed: -1,
laps: [],
lastClearedIncrementer: null
};
this.incrementer = null;
this.handleStartClick = this.handleStartClick.bind(this);
this.handleStopClick = this.handleStopClick.bind(this);
this.handleResetClick = this.handleResetClick.bind(this);
this.handleLapClick = this.handleLapClick.bind(this);
}
componentDidMount() {
if(this.state.secondsElapsed < 0) {
const getElapsedTime = async () => {
try{
const response = await fetch(`api url`);
if(response.ok){
let jsonResponse = await response.json();
/* start getting seconds elapsed since start */
let currentServerTime = new Date(jsonResponse[0].currentTimeStamp).getTime() /1000;
let currentD = Math.floor(new Date().getTime()/1000);
let deltaDate = (currentServerTime-currentD);
let raceStartTime = new Date(jsonResponse[0].startTime).getTime()/1000 - deltaDate;
let secondsElapsedSinceStart = currentD - raceStartTime;
/* end getting seconds elapsed since start */
this.props.changeElapsed(secondsElapsedSinceStart);
}
}
catch(error){
console.log(error);
}
}
getElapsedTime();
let newElapsed = this.props.elapsedSeconds;
this.incrementer = setInterval( () =>
this.setState({
secondsElapsed: newElapsed + 1
})
, 1000);
} else {
this.incrementer = setInterval( () =>
this.setState({
secondsElapsed: this.state.secondsElapsed + 1
})
, 1000);
}
}
handleStartClick() {
/* start post request */
const pushTime = async () => {
try {
const response = await fetch('apiurl', {
method: 'POST',
body: JSON.stringify({"startTime": "'2018-08-26 16:57:09'"})
})
if(response.ok){
const jsonResponse = await response.json();
return jsonResponse;
}
throw new Error('Request failed!');
} catch(error) {
console.log(error);
}
}
pushTime();
}
handleStopClick() {
clearInterval(this.incrementer);
this.setState({
lastClearedIncrementer: this.incrementer
});
}
handleResetClick() {
clearInterval(this.incrementer);
this.setState({
secondsElapsed: 0,
laps: []
});
}
handleLapClick() {
this.setState({
laps: this.state.laps.concat([this.props.elapsedSeconds])
})
}
render() {
return (
<div className="stopwatch">
<h1 className="stopwatch-timer">{formattedSeconds(this.state.secondsElapsed)}</h1>
{(this.props.elapsedSeconds === 0 ||
this.incrementer === this.state.lastClearedIncrementer
? <Button className="start-btn" onClick={this.handleStartClick.bind(this)}>start</Button>
: <Button className="stop-btn" onClick={this.handleStopClick.bind(this)}>stop</Button>
)}
{(this.props.elapsedSeconds !== 0 &&
this.incrementer !== this.state.lastClearedIncrementer
? <Button onClick={this.handleLapClick.bind(this)}>lap</Button>
: null
)}
{(this.props.elapsedSeconds !== 0 &&
this.incrementer === this.state.lastClearedIncrementer
? <Button onClick={this.handleResetClick.bind(this)}>reset</Button>
: null
)}
<ul className="stopwatch-laps">
{ this.state.laps.map((lap, i) =>
<li className="stopwatch-lap"><strong>{i + 1}</strong>/ {formattedSeconds(lap)}</li>)
}
</ul>
</div>
);
}
}
const Button = (props) =>
<button type="button" {...props} className={"btn " + props.className } />;
I found it easier to define a constant in the constructor like:
this.deltaTime = null;
Then I created a changeDeltaTime function:
changeDeltaTime(newTime) {
this.deltaTime = newTime
}
lastly, I updated that deltaTime inside the promiseand after the response within the componentDidUpdate() lifecycle method.
await this.changeDeltaTime(deltaDate);
In render, I waited the promise to be resolved through a prop passed by parent
return !this.props.finishedGet ? <span>Waiting on fetch...</span> : (this.renderStopwatch(this.state.time))
This also involves the renderStopwatch function to be created but it basically just houses what should be created once the promise resolves.

Rotating 3 pages in React

This is what I've been trying right now, but it keeps rendering the same page after the first switch.
I tried to follow the react lifecycle to figure it out, but it doesn't work as I intended.
I want it to start from RepoPage -> TimerPage -> ClockPage -> RepoPage and so on.
How can I fix it?
EDIT:
const REPO_PAGE = '5_SECONDS';
const COUNTDOWN_PAGE = '15_SECONDS';
const TIME_PAGE = '15_SECONDS';
const RepoComponent = () => (
<div>REPO</div>
);
const SprintCountComponent = () => (
<div>TIMER></div>
);
const DateTimeComponent = () => (
<div>CLOCK</div>
);
class App extends Component {
constructor(props) {
super(props);
this.state = {
repoTitles: [],
userData: [],
commitData: [],
recentTwoData: [],
currentPage: REPO_PAGE,
imgURL: ""
};
}
componentDidUpdate() {
const {currentPage} = this.state;
const isRepoPage = currentPage === REPO_PAGE;
const isTimePage = currentPage === TIME_PAGE;
if (isRepoPage) {
this._showDateTimePageDelayed();
} else if (isTimePage) {
this._showCountDownPageDelayed();
} else {
this._showRepoPageDelayed();
}
}
componentDidMount() {
this._showCountDownPageDelayed();
};
_showCountDownPageDelayed = () => setTimeout(() => {this.setState({currentPage: COUNTDOWN_PAGE})}, 5000);
_showRepoPageDelayed = () => setTimeout(() => {this.setState({currentPage: REPO_PAGE})}, 5000);
_showDateTimePageDelayed = () => setTimeout(() => {this.setState({currentPage: TIME_PAGE})}, 5000);
render() {
const {currentPage} = this.state;
const isRepoPage = currentPage === REPO_PAGE;
const isTimePage = currentPage === TIME_PAGE;
if(isRepoPage) {
return <RepoComponent/>;
} else if(isTimePage) {
return <DateTimeComponent/>;
} else {
return <SprintCountComponent/>;
}
}
}
You did not have return or else so this._showCountDownPageDelayed() is always executed.
if (isRepoPage) {
this._showDateTimePageDelayed();
} else if(isTimePage) {
this._showRepoPageDelayed();
} else {
this._showCountDownPageDelayed();
}
Using setInterval might give you a cleaner solution.
Edit: Your logic causes it to alternate between RepoPage and TimePage. A quick fix would be:
if (isRepoPage) {
this._showDateTimePageDelayed();
} else if (isTimePage) {
this._showCountDownPageDelayed();
} else {
this._showRepoPageDelayed();
}

Decrementing a number in React State

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,
}
});

Categories

Resources