Reputation: 1208
Console.log() always returns 5, so basically on frontend I always see this sentence: Welcome, you have successfully logged in, you will be redirected in.. 4
Looks like that because setRedirectSeconds() is asynchronous, it wont update state right at the moment and whenever my setInterval function starts again, redirectSeconds will still be 5 each time.
How to fix that?
Here is my code:
const Welcome: NextPage = (): ReactElement => {
const [redirectSeconds, setRedirectSeconds] = useState<number>(5);
const router = useRouter();
const query = router.query;
useEffect(() => {
if (query?.redirect) {
setTimeout(() => {
const interval = setInterval(() => {
console.log(redirectSeconds);
if (redirectSeconds > 0) {
setRedirectSeconds(redirectSeconds - 1);
} else {
clearInterval(interval);
router.push(query.redirect.toString());
}
}, 1000)
}, 1000);
}
}, []);
return (
<div>
Welcome, you have successfully logged in, you will be redirected in.. {redirectSeconds}
</div>
);
};
export default Welcome;
Upvotes: 6
Views: 7820
Reputation: 49331
Set a top level state variable. when it hits 0, redirect the user
import { useNavigate } from "react-router-dom";
const [count, setCount] = useState(5);
Inside useEffect write a setInterval
to decrease count by 1 after 1000ms
const navigate = useNavigate();
useEffect(()=>{
// each second count=count-1
const interval=setTimeInterval(()=>{
setCount((updatedCount)=>updatedCcount-1)
},1000)
// if count===0 redirect
count==0 && navigate(`/${query.redirect.toString()}`)
// always clear the timeers in return function
return ()=>{
clearIntercal(interval)
}
},[count,navigate])
then write the message.
p>
This page cannot be found. Redirecting to homepage in
{count} {count > 1 ? 'seconds' : 'second'}.
</p>
Upvotes: 0
Reputation: 81
You can use the below custom hook to achieve this functionality.
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
export default function useRedirectAfterSomeSeconds(redirectTo, seconds = 5) {
const [secondsRemaining, setSecondsRemaining] = useState(seconds);
const router = useRouter();
useEffect(() => {
if (secondsRemaining === 0) router.push('/');
const timer = setTimeout(() => {
setSecondsRemaining((prevSecondsRemaining) => prevSecondsRemaining - 1);
if (secondsRemaining === 1) router.push(redirectTo);
}, 1000);
return () => {
clearInterval(timer);
};
}, [router, secondsRemaining, redirectTo]);
return { secondsRemaining };
}
Usage example:
import Head from 'next/head';
import useRedirectAfterSomeSeconds from '../hooks/useRedirectAfterSomeSeconds';
export default function ErrorPage() {
const { secondsRemaining } = useRedirectAfterSomeSeconds('/', 5);
return (
<>
<Head>
<title>Page not found - redirecting in 5 seconds</title>
</Head>
<main>
<h1>404</h1>
<p>
This page cannot be found. Redirecting to homepage in
{secondsRemaining} {secondsRemaining > 1 ? 'seconds' : 'second'}.
</p>
</main>
</>
);
}
Upvotes: 1
Reputation: 1208
Here is how I adjusted it and now it works fine:
I have removed setInterval and added dependancy to useEffect function. And I have added timeout of 1000ms before decreasing redirectSeconds.
useEffect(() => {
if (query?.redirect) {
if (redirectSeconds == 0) {
router.push(query.redirect.toString());
return;
}
setTimeout(() => {
console.log(redirectSeconds);
setRedirectSeconds((redirectSeconds) => redirectSeconds - 1);
}, 1000)
}
}, [redirectSeconds]);
Upvotes: 7