I have created a localStorage that adds +1 every time the user visits my app. The problem is that every time I refresh the page, the value goes back to 0.
This is my code:
localStorage.setItem('timesVisited', 0);
useEffect(() => {
if (localStorage.getItem('timesVisited') < 5) {
let timesVisited = parseInt(localStorage.getItem('timesVisited'));
localStorage.setItem('timesVisited', ++timesVisited);
}
},[])
The idea is to make the localStorage sum +1 until it reaches 5 but it keeps on 0. What I'm doing wrong?
This line will always run and resets any previous value
localStorage.setItem('timesVisited', 0);
turn it into
if(!localStorage.getItem('timesVisited')){
localStorage.setItem('timesVisited', 0)
}
Try something like this, if localstorage not set then set 0 otherwise increment
useEffect(() => {
if (!localStorage.getItem('timesVisited')) {
localStorage.setItem('timesVisited', 0);
} else if (localStorage.getItem('timesVisited') < 5) {
let timesVisited = parseInt(localStorage.getItem('timesVisited'));
localStorage.setItem('timesVisited', ++timesVisited);
}
}, [])
If the lines you provided in the component's body, then not only will it be called every time a user visits your app, but every time the component renders.
For the behaviour you're looking for, you should load the value, and set it to 0 if it doesn't exist. You should do this outside of your component, too.
Also, note that localStorage only stores strings, so you'll want to parse what you fetch from it.
const storedValue = localStorage.getItem('timesVisited')
// localStorage only stores string
const visitsCount = storedValue ? parseInt(storedValue) : 0
function myComponent = () => {
useEffect(() => {
if(visitsCount >= 5){
return
}
localStorage.setItem('timesVisited', ++visitsCount);
}, [])
}
Related
In my React app I am showing a banner yes or no, based on React state and some values set in localStorage.
After close button is clicked, it's state showBanner is saved to localStorage and doesn't show the banner anymore
After 2 times using a page url in the React app with query param redirect=my-site it doesn't show the banner anymore:
import queryString from 'query-string';
const location = useLocation();
const queryParams = queryString.parse(location.search);
const [showBanner, setShowBanner] = useState(true);
const handleClick = () => {
setShowBanner(false);
localStorage.removeItem('redirect');
};
const hasQp = queryString
.stringify(queryParams)
.includes('redirect=my-site');
const initialCount = () => {
if (typeof window !== 'undefined' && hasQp) {
return Number(localStorage.getItem('redirect')) || 0;
}
return null;
};
const [count, setCount] = useState(initialCount);
const show = showBanner && hasQp && count! < 3;
useEffect(() => {
const data = localStorage.getItem('my-banner');
if (data !== null) {
setShowBanner(JSON.parse(data));
}
}, []);
useEffect(() => {
localStorage.setItem('my-banner', JSON.stringify(showBanner));
}, [showBanner]);
useEffect(() => {
let pageView = count;
if (pageView === 0) {
pageView = 1;
} else {
pageView = Number(pageView) + 1;
}
if (hasQp && showBanner === true) {
localStorage.setItem('redirect', String(pageView));
setCount(pageView);
}
}, []);
This is working fine (when you see some good code improvements let me know :) ).
But as soon the user clicks the close button I don't want the localStorage item redirect no longer appears. Now after refreshing the page it appears again.
How do i get this to work?
If this is executing when the page loads:
localStorage.setItem('redirect', String(pageView));
Then that means this is true:
if (hasQp && showBanner === true)
The hasQp value is true, which means this is true:
const hasQp = queryString
.stringify(queryParams)
.includes('redirect=my-site');
And showBanner is true because it's always initialized to true:
const [showBanner, setShowBanner] = useState(true);
It's not 100% clear to me why you need this state value, but you could try initializing it to false by default:
const [showBanner, setShowBanner] = useState(false);
But more to the point, I don't think you need this state value at all. It's basically a duplicate of data that's in localStorage. But since both state and localStorage are always available to you, I don't see a reason to duplicate data between them.
Remove that state value entirely and just use localStorage. An example of checking the value directly might be:
if (hasQp && JSON.parse(localStorage.getItem('my-banner')))
Which of course could be refactored to reduce code. For example, consider a helper function to get the value:
const showBanner = () => {
const data = localStorage.getItem('my-banner') ?? false;
if (data) {
return JSON.parse(data);
}
};
Then the check could be:
if (hasQp && showBanner())
There are likely a variety of ways to refactor the code, but overall the point is to not duplicate data. In thie case a value is being stored in localStorage instead of React state because it needs to persist across page loads. Just keep that value in localStorage and use it from there.
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;
}```
I'm trying to make a tinder-like clone where I want to show each 5 seconds a different user photo, I first get array of only images from matches and then with setInterval which I finally got to work inside effect I loop through images array and change active index of image, however when I console log active image, it's always the same image...
This always returns same active Index and same active image
console.log(images[activeImageIndex]);
useEffect(() => {
getImagesArray(mapStore.blurredMatches);
const interval = setInterval(() => {
if(images.length>0)
{
flipAndNextImage();
}
}, 5000);
return () => clearInterval(interval);
}, []);
const getImagesArray = (users) =>
{
let copy = [];
for(var i=0;i<users.length;i++)
{
copy.push(users[i].images[0]);
}
setImages(copy);
console.log('Setting images array',images);
};
const flipAndNextImage= () =>
{
console.log(images,'images');
if(activeImageIndex == images.length-1)
{
setActiveImageIndex(0);
console.log(images[activeImageIndex]);
}
else
{
setActiveImageIndex(activeImageIndex+1);
console.log(images[activeImageIndex]);
}
};
I believe this is because you update your activeImageIndex before logging the state
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.
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.