Use setInterval without increasing memory consuming - javascript

I use setInterval in my code to make my slider work. It should change image each 5 second . But I see that when I use it , it consume new memory all the time . How can I use setInterval without consuming new memory ?
const Slider = ({ children, sliderIndex, setSlideIndex }) => {
const classes = useStyles()
useInterval(() => {
setSlideIndex((sliderIndex + 1) % children.length)
}, 5000)
const animateFunc = useCallback(
(child, indx) => {
return (
(indx === sliderIndex && (
<div className={classes.root} key={indx}>
{children[sliderIndex].image}
</div>
)) ||
null
)
},
[children, classes.root, sliderIndex]
)
return children.map(animateFunc)
}
i use #use-it/interval hook for it https://www.npmjs.com/package/#use-it/interval

It seems unlikely to me that the interval is actually causing your issue here.
Below is a snippit that will run for two minutes - printing a new line to the console each second. Take a look at the memory usage over time in this page - it won't grow. You can even add a heavy for/while function into it if you like. I think your issue is elsewhere in your code and there are some good comments above to follow up.
let n = 1;
let myInterval = setInterval(function(){ console.log("hello number:" + n++); shouldIStop(120) }, 1000);
function shouldIStop(maxCount){
if (n >= maxCount){
clearInterval(myInterval);
}
}

Related

JS/React - Components lose touch functionality when passed through useEffect/setInterval?

I am making a simple space shooter style game. The bad guys will be sent to the screen in intervals (similar to tetris).
The bad guys have all their touch functionality working when I pass them in an array manually but when I try to send them to the screen in intervals they lose all touch functionality.
I first tried a useEffect hook with setInterval. The interval would filter the array of bad guys. That was where the problem first popped up. Then, I tried a custom useInterval hook and got the same result.
Lastly - rather than set the state of the array of onscreen villains, I thought I would keep the villains out of the setInterval and just increment indexes, and return a slice of them. This did allow the first villain to gain touch ability but ... I was again frustrated by failure.
Here is the offending code -
aliensArray is the array of alien components for the user to battle.
function runTheInterval(){
const difference = slice.right - slice.left;
if(slice.right < aliensArray.length-1){
slice.right += 1;
}
if (slice.left < slice.right && difference > 3 && difference < 5){
slice.left += 1;
}else if (slice.left < slice.right && slice.right === aliensArray.length-1){
slice.left += 1;
}
setSlice(() => {
return { right: slice.right, left: slice.left }
})
}
const interValue = useInterval(runTheInterval, 4000);
----Other code----
const liveOnScreen = aliensArray.slice(slice.left, slice.right);
<>
{liveOnScreen}</>
So the only thing to pass through the interval is the indices of the villains array to be filtered. What am I doing wrong??
Thanks
Here is the custom useInterval hook that I used.
const intervalRef = useRef(null);
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => savedCallback.current();
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(tick, delay);
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
return intervalRef;
}```

React: Handling focus using array of useRef

I have created a basic Todo app but having a hard time in implementing proper focus into it. Following are the requirements for focus:
Focus should be on the newly created input field when "Add" is clicked so the user can begin typing right away.
On deleting an item "Button X", focus will move to the input field in the row which replaced the deleted row, nowhere if there are no fields left, or on the new last row if the last row was deleted.
On moving an item up, focus should be placed on the newly moved field, and all associated buttons should move alongside the element. If a field is already at the top of a list, no reordering should occur, but focus should be transferred to the topmost field nonetheless.
On moving an item down, same principle should apply here (focus should be placed on the field that is moved down). If its the last field, focus it nonetheless.
Here is my implementation.
App.js:
import React, { useState, useRef } from "https://cdn.skypack.dev/react#17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom#17.0.1";
const App = () => {
const [myRows, setMyRows] = useState([]);
const focusInput = useRef([]);
const onAddRow = () => {
setMyRows((prevRows) => {
return [
...prevRows,
{ id: prevRows.length, text: "", up: "↑", down: "↓", delete: "X" },
];
});
focusInput.current[myRows.length - 1].focus();
};
const onMoveUp = (index) => (event) => {
const currentState = [...myRows];
if (index !== 0) {
const prevObject = currentState[index - 1];
const nextObject = currentState[index];
currentState[index - 1] = nextObject;
currentState[index] = prevObject;
setMyRows(currentState);
}
};
const onMoveDown = (index) => (event) => {
const currentState = [...myRows];
if (index !== myRows.length - 1) {
const currObject = currentState[index];
const nextObject = currentState[index + 1];
currentState[index] = nextObject;
currentState[index + 1] = currObject;
setMyRows(currentState);
}
};
const onDelete = (index) => (event) => {
const currentState = [...myRows];
currentState.splice(index, 1);
setMyRows(currentState);
};
const onTextUpdate = (id) => (event) => {
setMyRows((prevState) => {
const data = [...prevState];
data[id] = {
...data[id],
text: event.target.value,
};
return data;
});
};
return (
<div className="container">
<button onClick={onAddRow}>Add</button>
<br />
{myRows?.map((row, index) => {
return (
<div key={row.id}>
<input
ref={(el) => (focusInput.current[index] = el)}
onChange={onTextUpdate(index)}
value={row.text}
type="text"></input>
<button onClick={onMoveUp(index)}>{row.up}</button>
<button onClick={onMoveDown(index)}>{row.down}</button>
<button onClick={onDelete(index)}>{row.delete}</button>
</div>
);
})}
</div>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))
In my implementation when I click Add, focus changes but in unexpected way (First click doesn't do anything and subsequent click moves the focus but with the lag, i.e. focus should be on the next item, but it is at the prior item.
Would really appreciate if someone can assist me in this. Thanks!
The strange behaviour that you observe is caused by the fact that state updates are asynchronous. When you, in onAddRow, do this:
focusInput.current[myRows.length - 1].focus()
then myRows.length - 1 is still the last index of the previous set of rows, corresponding to the penultimate row of what you're actually seeing. This explains exactly the behaviour you're describing - the new focus is always "one behind" where it should be, if all you're doing is adding rows.
Given that description, you might think you could fix this by just replacing myRows.length - 1 with myRows.length in the above statement. But it isn't so simple. Doing this will work even less well, because at the point this code runs, right when the Add button is clicked, focusInput hasn't yet been adjusted to the new length, and nor in fact has the new row even been rendered in the DOM yet. That all happens a little bit later (although appears instantaneous to the human eye), after React has realised there has been a state change and done its thing.
Given that you are manipulating the focus in a number of different ways as described in your requirements, I believe the easiest way to fix this is to make the index you want to focus its own piece of state. That makes it quite easy to manage focus in any way you want, just by calling the appropriate state-updating function.
This is implemented in the code below, which I got working by testing it out on your Codepen link. I've tried to make it a snippet here on Stack Overflow, but for some reason couldn't get it to run without errors, despite including React and enabling Babel to transform the JSX - but if you paste the below into the JS of your Codepen, I think you'll find it working to your satisfaction. (Or, if I've misinterpreted some requirements, hopefully it gets you at least a lot closer than you were.)
Rather than just leaving you to study the code yourself though, I'll explain the key parts, which are:
the introduction of that new state variable I just mentioned, which I've called focusIndex
as mentioned, the calling of setFocusIndex with an appropriate value whenever rows are added, removed or moved. (I've been trying to follow your requirements here and it seems to work well to me, but as I said, I may have misunderstood.)
the key is the useEffect which runs whenever focusIndex updates, and does the actual focusing in the DOM. Without this, of course, the focus will never be updated on calling setFocusIndex, but with it, calling that function will "always" have the desired effect.
one last subtlety is that the "always" I put above is not strictly true. The useEffect only runs when focusIndex actually changes, but when moving rows there are some situations where it is set to the same value it had before, but where you still want to move focus. I found this happening when clicking outside the inputs, then moving the first field up or the last one down - nothing happened, when we want the first/last input to be focused. This was happening because focusIndex was being set to the value it already had, so the useEffect didn't run, but we still wanted it to in order to set the focus. The solution I came up with was to add an onBlur handler to each input to ensure that the focus index is set to some "impossible" value (I chose -1, but something like null or undefined would have worked fine as well) when focus is lost - this may seem artificial but actually better represents the fact that when the focus is on no inputs, you don't want to have a "sensible" focusIndex, otherwise the React state is saying one of the inputs is focused, when none are. Note that I also used -1 for the initial state, for much the same reason - if it starts at 0 then adding the first row doesn't cause focus to change.
I hope this helps and my explanations are clear enough - if you're confused by anything, or notice anything going wrong with this implementation (I confess I have not exactly tested it to destruction), please let me know!
import React, { useState, useRef, useEffect } from "https://cdn.skypack.dev/react#17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom#17.0.1";
const App = () => {
const [myRows, setMyRows] = useState([]);
const focusInput = useRef([]);
const [focusIndex, setFocusIndex] = useState(-1);
const onAddRow = () => {
setMyRows((prevRows) => {
return [
...prevRows,
{ id: prevRows.length, text: "", up: "↑", down: "↓", delete: "X" },
];
});
setFocusIndex(myRows.length);
};
useEffect(() => {
console.log(`focusing index ${focusIndex} in`, focusInput.current);
focusInput.current[focusIndex]?.focus();
}, [focusIndex]);
const onMoveUp = (index) => (event) => {
const currentState = [...myRows];
if (index !== 0) {
const prevObject = currentState[index - 1];
const nextObject = currentState[index];
currentState[index - 1] = nextObject;
currentState[index] = prevObject;
setMyRows(currentState);
setFocusIndex(index - 1);
} else {
setFocusIndex(0);
}
};
const onMoveDown = (index) => (event) => {
const currentState = [...myRows];
if (index !== myRows.length - 1) {
const currObject = currentState[index];
const nextObject = currentState[index + 1];
currentState[index] = nextObject;
currentState[index + 1] = currObject;
setMyRows(currentState);
setFocusIndex(index + 1);
} else {
setFocusIndex(myRows.length - 1);
}
};
const onDelete = (index) => (event) => {
const currentState = [...myRows];
currentState.splice(index, 1);
setMyRows(currentState);
const newFocusIndex = index < currentState.length
? index
: currentState.length - 1;
setFocusIndex(newFocusIndex);
};
const onTextUpdate = (id) => (event) => {
setMyRows((prevState) => {
const data = [...prevState];
data[id] = {
...data[id],
text: event.target.value,
};
return data;
});
};
return (
<div className="container">
<button onClick={onAddRow}>Add</button>
<br />
{myRows?.map((row, index) => {
return (
<div key={row.id}>
<input
ref={(el) => (focusInput.current[index] = el)}
onChange={onTextUpdate(index)}
onBlur={() => setFocusIndex(-1)}
value={row.text}
type="text"></input>
<button onClick={onMoveUp(index)}>{row.up}</button>
<button onClick={onMoveDown(index)}>{row.down}</button>
<button onClick={onDelete(index)}>{row.delete}</button>
</div>
);
})}
</div>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))

React state adds 1 but subtracts 2

I am writing a function to like/unlike posts and add or subtract 1 from the likes total. Something isn't right as the addition works correctly (adds 1) but it subtracts 2 for some reason. I am not sure if it's to do with the state update delay.
const Post = (props: PostProps) => {
const {
post: {
content,
author,
postedDate,
likes,
comments,
shares,
isLiked,
isBookmarked,
},
} = props;
const [isPostBookmarked, setIsPostBookmarked] = useState(isBookmarked);
const [isPostLiked, setIsPostLiked] = useState(isLiked);
const [likesCount, setLikesCount] = useState(likes);
const handlePostLikeClick = () => {
setIsPostLiked((prevIsLikedState) => {
setLikesCount((prevLikesCountState) => {
return prevIsLikedState
? prevLikesCountState - 1
: prevLikesCountState + 1;
});
return !prevIsLikedState;
});
return (
<IconButton
icon={Heart}
label={likesCount}
filled={isPostLiked}
onClick={handlePostLikeClick}
/>
)
};
You are nesting two different state updates in same function which leads to some fuzzy issues,
Can you try changing you handlePostLikeClick function to the one mentioned below??
const handlePostLikeClick = () => {
setLikesCount((prevLikesCountState) => {
return isPostLiked ? prevLikesCountState - 1 : prevLikesCountState + 1;
});
setIsPostLiked((prevIsLikedState) => {
return !prevIsLikedState;
});
};
You can use this code sandbox for reference
The answer given by #sumanth seems quite right, but it seems something missing. In the given answer, using same onClick function handlePostLikeClick, 2 states are going to be updated. But it should only be used to update isPostLiked. Because likesCount should be updated only if any state changes detected for isPostLiked state. Therefore, it is essential to use use effect as follows.
const [isPostLiked, setIsPostLiked] = useState(false);
const [likesCount, setLikesCount] = useState(1);
useEffect(() => {
setLikesCount((current) => (isPostLiked ? current + 1 : current - 1));
}, [isPostLiked]);
const handlePostLikeClick = () => {
setIsPostLiked((prevIsLikedState) => !prevIsLikedState);
};
Explantion
Initial state for likesCount used as 1, because during initial rendering use effect will be triggered and update likesCount to 0 (since isPostLiked initial state is false). Additionally, you can see likesCount increases its count only if isPostLiked is true.
But with #sumaneth answer, likesCount increases if isPostLiked is false.
isPostLiked ? prevLikesCountState - 1 : prevLikesCountState + 1;
Therefore, it's not tracking current change of isPostLiked state, but it's using its previous state to do the update on likesCount.
Therefore, you defenitely, need to use use effect to keep track on the current change of isPostLiked state and do the update on likesCount.

What am I doing bad with localStorage?

I am doing a time-to-click score table but first I have to localstorage each time I get in a game but I dont know how I have been trying everything but it still not working, I need to finish this fast, and I need help... otherwise I would try everyday to resolve this alone because I know that's the way to learn.. When I press the finished button It says that times.push() is not a function.
let times = Array.from(
{ length: 3 }
)
let interval2;
// Timer CountUp
const timerCountUp = () => {
let times = 0;
let current = times;
interval2 = setInterval(() => {
times = current++
saveTimes(times)
return times
},1000);
}
// Saves the times to localStorage
const saveTimes = (times) => {
localStorage.setItem('times', JSON.stringify(times))
}
// Read existing notes from localStorage
const getSavedNotes = () => {
const timesJSON = localStorage.getItem('times')
try {
return timesJSON ? JSON.parse(timesJSON) : []
} catch (e) {
return []
}
}
//Button which starts the countUp
start.addEventListener('click', () => {
timerCountUp();
})
// Button which stops the countUp
document.querySelector('#start_button').addEventListener('click', (e) => {
console.log('click');
times = getSavedNotes()
times.push({
score: interval2
})
if (interval) {
clearInterval(interval);
clearInterval(interval2);
}
})
You probably need to change the line times = JSON.parse(localStorage.times || []) to times = JSON.parse(localStorage.times) || [] this makes sure that if the parsing fails it will still be an array.
Even better is wrap them with try-catch just in case if your JSON string is corrupted and check if the time is an array in the finally if not set it to empty array.

Make countdown in background task on ios react-native

Right now I have this in my componentDidMount() methode:
setInterval(() => this.getTimeUntil(getTheTimeToAdd, count++), 1000);
It calls another methode for updating every second to count down. I have a problem when the user is counting down the application might close or the iPhone goes to sleep.
I have tried to use both:
react-native-mauron85-background-geolocation
and:
react-native-background-timer
To keep the count down running even when the iPhone sleeps og the application is closed. Any suggestions?
What you can do is create new Dates for each invocation to see the elapsed time. Like
setInterval(function() {
Date d = new Date();
if (this.oldDate && this.oldDate.getTime() + DELAY_THRESHHOLD < d.getTime()) {
...
this.oldDate = d;
}
else {
... //should anything be here?
}
},500);
Since you're already using react-native-background-timer,
you can use the setInterval from the BackgroundTimer instance.
Assuming that you've linked the project correctly, you should be able to do
// In the constructor or the state
this.state = {
initialCounter: 120000, //Equivalents to 2 minutes
active: false, // To show or hide a label
}
componentWillMount() {
this.setIntervalId = BackgroundTimer.setInterval(() => {
if (this.state.initialCounter === 0) {
this.setState({
initialCounter: this.state.initialCounter,
active: true,
})
} else {
this.setState({
initialCounter: this.state.initialCounter - 1000,
active: false,
})
}
}, 1000)
}
// Make sure to unmount the listener when component unmounts
componentWillUnmount() {
BackgroundTimer.clearInterval(this.setIntervalId)
}
// Helper func to reinitiate the counter
_onUpdate = () => {
this.setState({ initialCounter: 120000, active: false })
}
// And inside your render you may display the timer by formatting with moment()
The docs are a bit misleading, since setInterval and clearInterval works on IOS too. Tested on package 2.0.1
Try this code, it should catch up every time the app wakes up.
const duration = 15; // duration in minute
const alertAt = Date.now() + duration * 60 * 1000;
const interval = setInterval(() => {
const remaining = alertAt - Date.now();
if (remaining < 0) {
// time is up
clearInterval(interval);
return;
}
console.log(remaining);
}, 10)

Categories

Resources