React setTimeout and clearTimeout - javascript

Recently I am creating a website using React and I found out that I kind of use a lot of 'setTimeOut()' and I know that from React documentation sometimes you need to clean up some things when component unmount(To be honest, I am not fully understand about this clean up thing), apparently recently I saw a posts saying that 'setTimeOut()' need to be clean up too but how do I clean up functions that I call in 'useEffect()' which I am using 'setTimeOut()' inside the function?
Here are my codes:
useEffect(() => {
createContent();
handleMobileContainerView();
});
const createContent = () => {
if (contentCompShowStatus) {
for (let key in btnStatus) {
if (btnStatus.hasOwnProperty(key)) {
if (btnStatus[key] === true) {
if (key === 'aboutBtn') {
delayContent('about-contents');
} else if (key === 'skillsBtn') {
delayContent('skills-contents');
} else if (key === 'projectsBtn') {
delayContent('projects-contents');
}
}
}
}
}
};
const delayContent = (content) => {
if (firstTime) {
setTimeout(() => {
setCurrentContent(content);
setFirstTime(false);
}, 200);
} else if (!firstTime) {
setTimeout(() => {
setCurrentContent(content);
}, 450);
}
};
As you can see above codes, 'createContent()' is in 'useEffect()' and is calling a function name 'delayContent()' which is using 'setTimeout()'.
Do I need to do clean up with this kind of case?
How do I do clean up with this kind of case? (function inside of function which is using 'setTimeOut()' and is called in 'useEffect()')

You can return timerId while creating timeOut. And on unmount u can clean using return function of useEffect.
Unmount:
useEffect(() => {
const timerId = createContent();
handleMobileContainerView();
return () => {
clearTimeout(timerId);
};
}, []);
Return TimerId:
const delayContent = (content) => {
let timerId;
if (firstTime) {
timerId = setTimeout(() => {
setCurrentContent(content);
setFirstTime(false);
}, 200);
} else if (!firstTime) {
timerId = setTimeout(() => {
setCurrentContent(content);
}, 450);
}
return timerId;
};
// All code:
function A() {
useEffect(() => {
const timerId = createContent();
handleMobileContainerView();
return () => {
clearTimeout(timerId);
};
}, []);
const createContent = () => {
if (contentCompShowStatus) {
for (let key in btnStatus) {
if (btnStatus.hasOwnProperty(key)) {
if (btnStatus[key] === true) {
if (key === "aboutBtn") {
return delayContent("about-contents");
} else if (key === "skillsBtn") {
return delayContent("skills-contents");
} else if (key === "projectsBtn") {
return delayContent("projects-contents");
}
}
}
}
}
};
const delayContent = (content) => {
let timerId;
if (firstTime) {
timerId = setTimeout(() => {
setCurrentContent(content);
setFirstTime(false);
}, 200);
} else if (!firstTime) {
timerId = setTimeout(() => {
setCurrentContent(content);
}, 450);
}
return timerId;
};
}

You can use useEffect to return a function which should do the job of cleaning up of the setTimouts and setIntervals. For eg,
useEffect(() => {
const timer = setTimeout(someFunc, 100);
return () => clearTimeout(timer);
});
To cleanup setTimouts, use clearTimeout and clearInterval for setInterval. Documentation
As far as your code is concerned,
useEffect(() => {
const timers = createContent();
handleMobileContainerView();
return () => timers.forEach(timer => window.clearTimeout(timer));
});
const createContent = () => {
let timers = [];
if (contentCompShowStatus) {
for (let key in btnStatus) {
if (btnStatus.hasOwnProperty(key)) {
if (btnStatus[key] === true) {
if (key === 'aboutBtn') {
timers.push(delayContent('about-contents'));
} else if (key === 'skillsBtn') {
timers.push(delayContent('skills-contents'));
} else if (key === 'projectsBtn') {
timers.push(delayContent('projects-contents'));
}
}
}
}
}
return timers;
};
const delayContent = (content) => {
let timer;
if (firstTime) {
timer = setTimeout(() => {
setCurrentContent(content);
setFirstTime(false);
}, 200);
} else if (!firstTime) {
timer = setTimeout(() => {
setCurrentContent(content);
}, 450);
}
return timer;
};

Related

Integrating debounce to search input

I am trying to integrate debounce to a search input so i can search through fetched data (after getting the data ) with the full term, i have this debounce function in the App.js :
debounce = (callback, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => callback(...args), delay);
}
}
debouncedTerm = this.debounce(text => console.log(text), 300);
I have also this method in the App.js
onSearchChanged = data => {
const searchBar = data.target.value.toLowerCase();
this.debouncedTerm(searchBar)
this.setState({
searchBar: searchBar,
}, () => {
let newData = {...this.state};
if( typeof this.props.source !== "undefined"){
newData.source = this.props.source
}
newData.params = {
searchBar : this.state.searchBar
}
Api.fetchQuestions(newData.params)
.catch( error => {
if (typeof error.message !== "undefined"){
newData.config.formMessage = {
message : error.message,
success : false,
}
newData.widgetStatus = false;
this.setState(newData)
}
return {data:{data:[]}}
})
.then( response => {
if(response.data.status === 'success'){
return response.data.data;
}
this.setState({
questions: [...response.data.questions.items],
loading: false
});
})
});
};
FYI : I pass the value of the input (searchBar) as a parameter in newData.params
'URL/questions?client_id='+params.client_id+'&term='+params.searchBar,
How can i integrate debouncedTerm in the method onSearchChanged
I have the search input in a child component
<InputSearch
type="search"
placeholder="Search"
onChange={props.searchCallback}
/>
this is a part of the code I wrote recently:
export function debounceFetch(fn, delay) {
let createdTimeout;
let controller;
function onCancel() {
controller?.abort();
createdTimeout?.cancel();
}
async function debounced(...args) {
onCancel();
controller = new AbortController();
createdTimeout = timeout(delay);
await createdTimeout.delay;
return fn(...args, controller.signal);
}
return [
debounced,
onCancel,
];
}
It cancels the fetch request too,
and this is the timeout part:
function timeout(ms) {
let timeoutRef;
let rejectRef;
const delay = new Promise((resolve, reject) => {
timeoutRef = setTimeout(() => {
resolve('timeout done');
}, ms);
rejectRef = () => {
reject(new Error('cancel timeout'));
};
});
return {
delay,
cancel() {
clearTimeout(timeoutRef);
rejectRef();
},
};
}

Implementing my own Promise in JavaScript

I'm trying to implement my own promise in JavaScript. Below is my class.
const states = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected"
}
class MyPromise {
constructor(computation) {
this.state = states.PENDING
this.value = undefined;
this.error = undefined;
this.thenQueue = [];
if(typeof computation === 'function') {
setTimeout(() => {
try {
computation(
this.onFulFilled.bind(this),
this.onReject.bind(this)
);
} catch(ex) {
this.onReject.bind(this);
}
});
}
}
then = (fullfilledFn, catchFn) => {
const promise = new MyPromise();
this.thenQueue.push([promise, fullfilledFn, catchFn]);
if(this.state === states.FULFILLED) {
this.propageFulFilled()
} else if(this.state == states.REJECTED) {
this.propageRejected();
}
}
catch = (catchFn) => {
return this.then(undefined, catchFn);
}
onFulFilled = (value) => {
if(this.state === states.PENDING) {
this.state = states.FULFILLED;
this.value = value;
this.propageFulFilled();
}
}
onReject = (error) => {
if(this.state === states.PENDING) {
this.state = states.REJECTED;
this.error = error;
this.propageRejected();
}
}
propageFulFilled = () => {
for(const [promise, fullFilledFn] of this.thenQueue) {
const result = fullFilledFn(this.value);
if(typeof result === 'MyPromise') {
promise.then(
value => promise.onFulFilled(value),
error => promise.onReject(error)
)
} else {
promise.onFulFilled(result); // final result
}
}
this.thenQueue = [];
}
propageRejected = () => {
for(const [promise, undefined, catchFn] of this.thenQueue) {
const result = catchFn(this.value);
if(typeof result === 'MyPromise') {
promise.then(
value => promise.onFulFilled(value),
error => promise.onReject(error)
)
} else {
promise.onFulFilled(result); // final result
}
}
this.thenQueue = [];
}
}
If I call the code below, it works fine
const testPromise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve(10));
});
const firstCall = testPromise.then((value) => {
console.log(value)
return value+1;
});
However, if I add a second then to my firstCall request as below:
const firstCall = testPromise.then((value) => {
console.log(value)
return value+1;
}).then((newVal) => {
console.log(newVal)
});
I got the error TypeError: Cannot read property 'then' of undefined. Does anyone know why it is happening?
Thanks
Your then function is not returning anything:
then = (fullfilledFn, catchFn) => {
const promise = new MyPromise();
this.thenQueue.push([promise, fullfilledFn, catchFn]);
if(this.state === states.FULFILLED) {
this.propageFulFilled()
} else if(this.state == states.REJECTED) {
this.propageRejected();
}
return promise;
}

Clear all kind of intervals

Imagine the scenario in which you have the main function which executes 2 functions which start intervals. These functions are imported as a NodeJS module and executed. Then in the main function after some time you clearIntervals. Also, note that there we'll be more intervals in the main function in the future.
So the main function is
(() => {
const intervals = [];
intervals.push(require('./simpleInterval')());
intervals.push(require('./asyncInterval')());
setTimeout(() => {
intervals.forEach(id => clearInterval(id));
}, 1200)
})();
One of these methods is simply
const intervalFoo = () => {
return setInterval(() => {
console.log('interval simple')
}, 500);
};
module.exports = intervalFoo;
but the second one contains some asynchronous code which can perform longer than interval gap but I don't want it to start before the previous "iteration" didn't finish. The solution in this kinda situation is to clear interval by id at the beginning and then reassign it at the end (but within the body) of interval. So the code of asyncInterval.js is:
const sleep = require('./utilities/sleep');
const intervalFoo = () => {
let intervalId;
const checkE2Interval = async() => {
clearInterval(intervalId);
console.log('interval async');
await sleep(120); //some long action
return intervalId = setInterval(checkE2Interval, 100);
};
return intervalId = setInterval(checkE2Interval, 100); //returning id
};
module.exports = intervalFoo;
(sleep is just a promise which resolves after timeout time given as argument)
The issue about this is that I'm returning intervalId from asyncInterval.js also in the interval and my problem is that I don't know how am I suppose to clear this thing.
Provide cancellation functions rather than providing raw handles, and pass your function an object with a flag it can check for whether it's been cancelled:
function mySetInterval(callback, ms, ...args) {
let token = {
cancelled: false
};
function wrapper(...args) {
callback(token, ...args);
if (!token.cancelled) {
id = setTimeout(wrapper, ms, ...args);
}
}
let id = setTimeout(wrapper, ms, ...args);
return function cancel() {
clearInterval(id);
token.cancelled = true;
}
}
Since 0 is an invalid timer ID, we can safely use it as a flag that the interval has been cancelled. Note that there is a slight difference between chained setTimeout (above) and setInterval (setInterval's handling of the delay between intervals is...interesting.) Also note that nothing above prevents the function being called while it's paused on sleep. To do that, you'd have to have a guard and to have the function specifically support async functions:
function mySetInterval(callback, ms, ...args) {
let token = {
cancelled: false
};
let running = false;
async function wrapper(...args) {
if (!running) {
running = true;
await callback(token, ...args);
running = false;
}
if (!token.cancelled) {
id = setTimeout(wrapper, ms, ...args);
}
}
let id = setTimeout(wrapper, ms, ...args);
return function cancel() {
clearInterval(id);
token.cancelled = true;
}
}
Use that function instead of setInterval.
In your async function, if it never has reason to stop itself:
const intervalFoo = () => {
const checkE2Interval = async(token) => {
console.log('interval async');
await sleep(120); //some long action
// If you had more logic here, you could short-circuit it by checking token.cancelled
};
return mySetInterval(checkE2Interval, 100); //returning id
};
If it does have reason to stop itself, save cancel:
const intervalFoo = () => {
let cancel = null;
const checkE2Interval = async(token) => {
console.log('interval async');
await sleep(120); //some long action
// If you had more logic here, you could short-circuit it by checking token.cancelled
// If you wanted not to continue the timer, you'd call cancel here
};
return cancel = mySetInterval(checkE2Interval, 100); //returning id
};
Then, where you need to cancel:
(() => {
const cancellers = [];
cancellers.push(require('./simpleInterval')());
cancellers.push(require('./asyncInterval')());
setTimeout(() => {
cancellers.forEach(cancel => cancel());
}, 1200)
})();
Live Example:
const sleep = ms => new Promise(resolve => {
setTimeout(resolve, ms);
});
function mySetInterval(callback, ms, ...args) {
let token = {
cancelled: false
};
function wrapper(...args) {
callback(token, ...args);
if (!token.cancelled) {
id = setTimeout(wrapper, ms, ...args);
}
}
let id = setTimeout(wrapper, ms, ...args);
return function cancel() {
clearInterval(id);
token.cancelled = true;
}
}
const intervalFoo = () => {
let cancel = null;
const checkE2Interval = async(token) => {
console.log('interval async');
await sleep(120); //some long action
// If you had more logic here, you could short-circuit it by checking token.cancelled
// If you wanted not to continue the timer, you'd call cancel here
};
return cancel = mySetInterval(checkE2Interval, 100); //returning id
};
(() => {
const cancellers = [];
cancellers.push(intervalFoo());
setTimeout(() => {
console.log("Cancelling");
cancellers.forEach(cancel => {
cancel();
});
}, 1200)
})();
Live Example with the running flag:
const sleep = ms => new Promise(resolve => {
setTimeout(resolve, ms);
});
function mySetInterval(callback, ms, ...args) {
let token = {
cancelled: false
};
let running = false;
async function wrapper(...args) {
if (!running) {
running = true;
await callback(token, ...args);
running = false;
}
if (!token.cancelled) {
id = setTimeout(wrapper, ms, ...args);
}
}
let id = setTimeout(wrapper, ms, ...args);
return function cancel() {
clearInterval(id);
token.cancelled = true;
}
}
const intervalFoo = () => {
let cancel = null;
const checkE2Interval = async(token) => {
console.log('interval async');
await sleep(120); //some long action
console.log('awake');
// If you had more logic here, you could short-circuit it by checking token.cancelled
// If you wanted not to continue the timer, you'd call cancel here
};
return cancel = mySetInterval(checkE2Interval, 100); //returning id
};
(() => {
const cancellers = [];
cancellers.push(intervalFoo());
setTimeout(() => {
console.log("Cancelling");
cancellers.forEach(cancel => {
cancel();
});
}, 1200)
})();
You can generalize this further, but you get the basic idea.

Pass a parameter into a function setting a setTimeout?

Is there a way I can call this from another module specifying the timeout in brackets?
var addActiveDiv = () => {
var activeEl = document.querySelector(".o-wrapper");
setTimeout(() => {
activeEl.classList.add("is-active");
}, 1850);
}
addActiveDiv();
export { addActiveDiv }
Something like
addActiveDiv(2000); where 2000 is the new timeout?
Just specify the function as taking a parameter, and pass that parameter to setTimeout?
var addActiveDiv = (ms) => {
var activeEl = document.querySelector(".o-wrapper");
setTimeout(() => {
activeEl.classList.add("is-active");
}, ms);
}
addActiveDiv(2000);
export { addActiveDiv }
Use parameters or default parameters.
var addActiveDiv = (timeout = 1850) => {
var activeEl = document.querySelector(".o-wrapper");
setTimeout(() => {
activeEl.classList.add("is-active");
}, timeout);
}
addActiveDiv();
export { addActiveDiv }
import { addActiveDiv } from "foobar";
addActiveDiv(2000);

Unable to syncronize the functions

I'm trying to execute the functions one by one in synchronization.
var subtasks = ['Site', 'Draw', 'Material', 'Conduction', 'Cable', 'Install', 'Foundation']
function clickMe() {
return new Promise(resolve => {
jQuery("#addtoptasksubmit").trigger("click"); // triggering the button click
resolve("done click");
});
}
function typeWord(word) {
return new Promise(resolve => {
jQuery("#todotask").val(word); // input
resolve("done type");
});
}
function createSubTask() {
return new Promise(res => {
jQuery('#todotask').focus();
res("done")
})
};
function startLoop(i) {
new Promise(resolve => {
var promise = createSubTask();
promise.then(resolve => {
var typePromise = typeWord(subtasks[i]);
typePromise.then((resolve) => {
var clickPromise = clickMe();
clickPromise.then((resolve) => {
console.log(resolve);
});
});
});
})
}
let i = 0;
let prom = startLoop(i);
prom.then((res) => {
startLoop(i++);
})
code is not working properly and also I wanted to increment i automatically. For loop is a no show.
I've tried for loop and recursive async function on chrome.
Doesn't your startLoop(i) function need a return statement for the new Promise() call? I have to imagine this:
// prom = undefined as startLoop never returns anything
let prom = startLoop(i);
Change your code like so:
// Current code from description
function startLoop(i) {
new Promise(resolve => {
// Fixed code
function startLoop(i) {
return new Promise(resolve => {
var subtasks = ['Site', 'Draw', 'Material', 'Conduction', 'Cable', 'Install', 'Foundation'];
function clickMe() {
return new Promise(resolve => {
setTimeout(() => {
jQuery("#addtoptasksubmit").trigger("click");
resolve("done click");
}, 1000);
});
}
function typeWord(word) {
return new Promise(resolve => {
jQuery("#todotask").val(word);
resolve("done type");
});
}
function createSubTask() {
return new Promise(res => {
jQuery('#todotask').focus();
res("done");
})
};
function startLoop(i) {
return new Promise(res => {
var promise = createSubTask();
promise.then(
(resolve) => {
var typePromise = typeWord(subtasks[i]);
typePromise.then((resolve) => {
console.trace(resolve);
var clickPromise = clickMe();
clickPromise.then((resolve) => {
console.trace(resolve);
res("done loop " + subtasks[i]);
});
});
}
);
})
}
var _index_ = 0;
var _how_ = setInterval(() => {
if (_index_ < subtasks.length) {
let loopPromise = startLoop(_index_);
loopPromise.then((resolve) => {
_index_ += 1;
});
} else {
console.log("all done go get the new task");
clearInterval(_how_);
}
}, 10000);
I can optimise it further....

Categories