clearInterval not clearing the set interval onMouseUp - javascript

Trying to make a button event onMouseDown, a function should run at the end of the set amount of time. The function runs onMouseDown and clears the interval onMouseUp, but the interval still runs after releasing the button.
This is the code currently. I have the interval global and set it in the planting function. It should unset in the notPlanting function, but it does not.
import React from "react";
function PlantDefuser() {
var interval
function planting() {
interval = setInterval(() => {
console.log("Defuser Planted")
}, 1000)
}
function notPlanting() {
console.log(interval)
clearInterval(interval)
}
return (
<button onMouseDown={planting} onMouseUp={notPlanting}>Press and Hold</button>
)
}
export default PlantDefuser

This could help you:
useRef allows us to store and update data in the component without triggering a re-render. Now the only re-render happens when the props are updated.
We can store interval in a ref like so
import { useRef } from "react";
const PlantDefuser = () => {
const interval = useRef();
function planting() {
interval.current = setInterval(() => {
console.log("Defuser Planted");
}, 1000);
}
function notPlanting() {
clearInterval(interval.current);
}
return (
<button onMouseDown={planting} onMouseUp={notPlanting}>
Press and Hold
</button>
);
}
export default PlantDefuser

When you declare variables like so in the function component, it is being created on each render. You should be saving the interval id in a state like so:
import React, { useState } from "react";
const PlantDefuser = () => {
const [plantingInterval, setPlantingInterval] = useState(null);
const planting = () => {
const plantingIntervalId = setInterval(() => {
console.log("Defuser Planted");
}, 1000);
setPlantingInterval(plantingIntervalId);
};
const notPlanting = () => {
clearInterval(plantingInterval);
setPlantingInterval(null);
};
return (
<button onMouseDown={planting} onMouseUp={notPlanting}>
Press and Hold
</button>
);
};
export default PlantDefuser;
You might also want to make sure the interval is being cleared when the component unmounts.

You can use useEffect hook with cleanup function to manage the clearInterval method .
like this :
function PlantDefuser() {
const [run, setRun] = useState(false);
useEffect(() => {
if (run) {
const countTimer = setInterval(() => {
console.log("Defuser Planted");
}, 1000);
return () => {
console.log(countTimer);
clearInterval(countTimer);
};
}
}, [run]);
return (
<button onMouseDown={() => setRun(!run)} onMouseUp={() => setRun(!run)}>
Press and Hold
</button>
);
}
export default PlantDefuser;

Related

functional component still rerender with react.memo

I want to make a quiz app with a countdown timer.
The timer use setinterval and the quiz has a functional component for Question.
I have already set react.memo for the question function component. But it keeps rerender each second. It works fine on desktop. But it flickers on mobile since the input for answer keep refocus
import { useEffect, useState, memo } from 'react';
function Game() {
let interval;
const [timer, setTimer] = useState(180000);
useEffect(() => {
interval = setInterval(() => {
setTimer(timer - 1000);
}, 1000);
return () => {
if (interval) {
clearInterval(interval)
}
}
}, [timer]);
const QuestionComponent = memo(props => {
return(<div></div>);
});
return (
<>
<div>{timer}</div>
<QuestionComponent/>
</>
);
}
The above code is the Game component, which will be called from App component
The question component keeps rerendering even there is nothing in it.
May anyone please advise? Thank you very much
Ideally you should define your components outside your other components. As mentioned in comment, this line of code will run on every render.
Even if you use React.memo(), this line itself will run on every render, in contrast to writing it outside where it will run normally.
If you are writing it inside, you have to persist the value between renders. It can be done using useCallback because a react component is technically a function:
const Component = useCallback(
memo(() => {
console.log("rerender");
return <div></div>;
}),
[]
);
Or another way is using a ref.
import { useEffect, useState, memo, useMemo, useCallback, useRef } from "react";
function App() {
let interval;
const [timer, setTimer] = useState(180000);
const componentRef = useRef();
useEffect(() => {
interval = setInterval(() => {
setTimer(timer - 1000);
}, 1000);
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [timer]);
useEffect(() => {
const Component = memo(() => {
console.log("rerender");
return <div></div>;
});
componentRef.current = Component;
}, []);
return (
<>
<div>{timer}</div>
<>{componentRef.current && <componentRef.current />}</>
</>
);
}
export default App;
Link
But both above are hacks. And you will definitely run into issues when you use hooks.
Much better to just write it outside.
const QuestionComponent = memo(props => {
return(<div></div>);
});
import { useEffect, useState, memo } from 'react';
function Game() {
let interval;
const [timer, setTimer] = useState(180000);
useEffect(() => {
interval = setInterval(() => {
setTimer(timer - 1000);
}, 1000);
return () => {
if (interval) {
clearInterval(interval)
}
}
}, [timer]);
return (
<>
<div>{timer}</div>
<QuestionComponent/>
</>
);
}

Why eventListener re-render React Component

I am creating a stopwatch in React.js and i am wondering why window.addEventListener('keydown', callback) re-render my component?
import { useEffect, useState } from 'react';
import './App.scss';
import Timer from './Timer';
import Button from './Button';
import Time from './Time';
const App = () => {
const [isRunning, setIsRunning] = useState(false);
const [start, setStart] = useState(new Time(0));
const [stop, setStop] = useState(new Time(0));
const handleStart = () => {
const now = new Date();
setIsRunning(true);
setStart(new Time(now));
setStop(new Time(now));
};
const handleStop = () => {
setIsRunning(false);
setStop(new Time(new Date()));
};
const getTime = () => {
if (isRunning) {
return new Time(new Date().getTime() - start.origin);
} else {
return new Time(stop.origin - start.origin);
}
};
const handleKeyDown = (key) => {
console.log(key.code === 'Space');
};
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
});
return (
<div className="stopwatch">
<Timer getTime={getTime} />
<div className="buttons">
<Button role={'start'} callback={handleStart} />
<Button role={'stop'} callback={handleStop} />
</div>
</div>
);
};
export default App;
When i click start and then stop after let's say 3s. <Timer /> show correctly time that has passed, but then when i press Space on keyboard <Timer /> is re-rendering, showing new time. Then, when i switch my web-browser to VSCode and again to web-browser, <Timer /> isn't re-rendering
Here is my Timer component
import { memo, useEffect, useRef } from 'react';
const Timer = ({ getTime }) => {
const timer = useRef();
console.log('timer rendered');
useEffect(() => {
function run() {
const time = getTime().formatted();
timer.current.textContent = `${time.m}:${time.s}.${time.ms}`;
requestAnimationFrame(run);
}
run();
return () => {
cancelAnimationFrame(run);
};
});
return <div ref={timer} className="timer"></div>;
};
export default memo(Timer);
no matter if I use [] in both or none of useEffect nothing changes.
As #davood-falahati says, adding an empty array as a second argument to useEffect would probably be desirable. From the docs:
... If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works. ...
In your use case:
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, []);

How to call a function every minute in a React component?

I make a table to get stock price quotes, it works well, but when I try to put a function include setState in the component, it falls into an infinite loop, it triggers setState and re-render immediately and triggers again.
How can I call this function without triggering an infinite loop when I load this component?
I would to call the function every 10 seconds or every minute.
import React, { useState } from 'react'
import api from '../../api'
function CreateRow(props){
const [stock, setStock] = useState({symbol:'',last:'',change:''})
async function check() {
const result = await api.getStock(props.item)
console.log(props.item)
const symbol = result.data.symbol
const lastest = result.data.latestPrice
const change = result.data.change
setStock({symbol:symbol, lastest:lastest, change:change})
}
// check() <----------! if I call the function here, it becomes an infinite loop.
return(
<tr>
<th scope="row"></th>
<td>{stock.symbol}</td>
<td>{stock.lastest}</td>
<td>{stock.change}</td>
</tr>
)
}
export default CreateRow
You want to initiate a timeout function inside a lifecycle method.
Lifecycle methods are methods which call on, for example, mount and unmount (there are more examples but for the sake of explanation I will stop here)
what you're interested in is the mount lifecycle.
In functional components, it can be accessed like this:
import { useEffect } from 'react';
useEffect(() => {
// This will fire only on mount.
}, [])
In that function, you want to initialize a setTimeout function.
const MINUTE_MS = 60000;
useEffect(() => {
const interval = setInterval(() => {
console.log('Logs every minute');
}, MINUTE_MS);
return () => clearInterval(interval); // This represents the unmount function, in which you need to clear your interval to prevent memory leaks.
}, [])
Consider 60000 milliseconds = 1 minute
Can do using the method:
setInterval(FunctionName, 60000)
do as below:
async function check() {
const result = await api.getStock(props.item)
console.log(props.item)
const symbol = result.data.symbol
const lastest = result.data.latestPrice
const change = result.data.change
setStock({symbol:symbol, lastest:lastest, change:change})
}
// Write this line
useEffect(() => {
check()
}, []);
setInterval(check, 60000);
you also do that with setTimeout
import React, { useState, useEffect } from "react";
export const Count = () => {
const [counts, setcounts] = useState(0);
async function check() {
setcounts(counts + 1);
}
// Write this line
useEffect(() => {
check();
}, []);
console.log("hello dk - ",counts)
setTimeout(() => {
check();
}, 1000);
return <div>Count : - {counts}</div>;
};
import React, { useState, useEffect } from "react";
export const Count = () => {
const [currentCount, setCount] = useState(1);
useEffect(() => {
if (currentCount <= 0) {
return;
}
const id = setInterval(timer, 1000);
return () => clearInterval(id);
}, [currentCount]);
const timer = () => setCount(currentCount + 1);
console.log(currentCount);
return <div>Count : - {currentCount}</div>;
};

Component doesn't update once the parents state is changed

I'm attempting to get a timer to call a function quickly at first and then slow down. I have a TimeInterval state that increases and is passed down to my countdown component as a prop
<Countdown
isActive={RandominatorRunning}
target={() => NextSelection()}
timeToChange={TimeInterval}
/>
Countdown Component
import React, { useEffect } from 'react';
const Countdown = ({ isActive, target, timeToChange }) => {
useEffect(() => {
let interval = null;
if (isActive) {
interval = setInterval(() => {
target()
}, timeToChange)
} else if (!isActive) {
clearInterval(interval)
}
return () => clearInterval(interval)
}, [isActive])
return null
}
export default Countdown;
My TimeInterval state is working properly and will increase as NextSelection() is called. However this doesn't seem to increase the interval of the countdown component and NextSelection() is always called at the same pace, not at the changing state TimeInterval pace. Why is the countdown component not updating it's pace along with the TimeInterval state?
Not positive this is the best solution, but I was able to alter my countdown component to get the desired effect.
I changed my countdown component to become inactive while it executes the prop update, then resumes as soon as the prop has finished updating.
import React, { useEffect, useState } from 'react';
const Countdown = ({ isActive, target, timeToChange }) => {
const [Active, setActive] = useState(isActive)
const handleTimeExpire = async () => {
await target()
setActive(true)
}
useEffect(() => {
let interval = null;
if (Active) {
interval = setInterval(() => {
setActive(false)
handleTimeExpire()
}, timeToChange)
} else if (!Active) {
clearInterval(interval)
}
return () => clearInterval(interval)
}, [Active])
return null
}
export default Countdown;

Trying to change a state in React Hooks with Interval

I have a problem trying to change a state in react using setInterval.
Why does the function "alert" always show a "null" message?
import React, { useEffect, useState } from 'react';
const Playlist = (props) => {
const [test, setTest] = useState("");
useEffect(() => {
setInterval(load, 3000)
}, []);
const load = async () => {
alert(test)
setTest("TEST");
}
return (
<div>
{test}
</div>
)
}
export default Playlist
Issue :
is callback passed into setInterval's closure and it accesses the test variable in the first render.
Solution :
You can clearInterval and setInterval as soon as there is any changes in your state
const { useState , useEffect , useCallback } = React;
const App = () => {
const [test, setTest] = useState("");
useEffect(() => {
const intrvl = setInterval(load, 3000);
return () => clearInterval(intrvl);
}, [test]);
const load = () => {
alert(test)
setTest("TEST");
}
return (
<div>
{test}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>
The problem is that useEffect captures the test from the first render which is equal to empty string. We never re-apply the effect so the closure in setInterval always references the test from the first render.
NOTE: setInterval does not describe a process in time — once you set the interval, you can’t change anything about it except clearing it.
When we pass an empty [] array of dependencies to useEffect, it only runs the useEffect on mount and cleanup on unmount of the component.
One way is to add test to the array of dependencies so anytime there is an update on test, the useEffect will be called.
useEffect(() => {
const intervalId = setInterval(load, 3000);
return () => clearInterval(intervalId);
}, [test]);
But since we are using the test inside useEffect, we can just remove the empty [] array of dependencies so the useEffect will be called every time we are setting the state (component is re-rendered) and the new setInterval will be created with accessing to the latest test state like below
useEffect(() => {
const intervalId = setInterval(load, 3000);
return () => clearInterval(intervalId);
});
Another possible way is to update the state inside the interval's closure like
useEffect(() => {
const id = setInterval(() => {
setTest("TEST");
}, 3000);
return () => clearInterval(id);
});
const App = () => {
const [test, setTest] = useState("");
useEffect(() => {
const intervalId = setInterval(load, 3000);
return () => clearInterval(intervalId);
});
const load = () => {
alert(test)
setTest("TEST");
}
return (
<div>
{test}
</div>
)
}
Example of interval with setting count so you can see the state change

Categories

Resources