setInterval problem and useState doesn't change - javascript

I tried to count down the second and reload the page, the second showed in the component is correct but the printed on the console was always 5, so I couldn't reload.
I want to count down and reload the page. Where do I put window.location.reload()? and why count always showed 5, but setCount is normal and decrease every second?
This is the code:
function Register() {
const dispatch = useDispatch();
const [count, setCount] = useState(5);
const timeountToReload = () => {
const interval = setInterval(() => {
console.log(count); // always 5 here
if (count === 0) {
window.history.pushState({}, '', '/');
window.location.reload();
}
setCount((count) => count - 1);
}, 1000);
return () => clearInterval(interval);
};
const handleRegister = async (e, value) => {
e.preventDefault();
setSuccessful(false);
setFormControlDisable(true);
await dispatch(register({ username, email, password }))
.then(unwrapResult)
.then((result) => {
setSuccessful(true);
timeountToReload();
})
.catch((error) => {});
setFormControlDisable(false);
};
return (
<Container style={{ marginTop: '15%' }}>
<Row className='justify-content-md-center'>
<Col md={{ span: 4, offset: 0 }} style={{ border: 'green solid 5px' }}>
{count}
<RegisterForm
handleUsernameChange={handleUsernameChange}
handlePasswordChange={handlePasswordChange}
handleComfirmedPasswordChange={handleComfirmedPasswordChange}
handleEmailChange={handleEmailChange}
handleRegister={handleRegister}
username={username}
password={password}
comfirmedPassword={comfirmedPassword}
email={email}
errors={errors}
message={message}
successful={successful}
count={count} // component shows normal and refresh every second
formControlDisable={formControlDisable}
/>
</Col>
</Row>
</Container>
);
}
export default Register;

I think that inside the interval you passed a function instead of a value to the setCount. try to replace
setCount(count => count - 1)
with
setCount(count - 1)

Try to keep track of count changes using the useEffect hook:
const timeountToReload = () => {
const interval = setInterval(() => {
setCount((count) => count - 1);
}, 1000);
return () => clearInterval(interval);
};
useEffect(() => {
console.log(count);
if (count === 0) {
window.location.reload();
}
}, [count]);
Link to playground.

you should use useEffect with the count as dependency to watch its chages, because inside setInterval it won't update
useEffect(() => {
if (count === 0) {
window.history.pushState({}, '', '/');
window.location.reload();
};
}, [count]);

Related

React component renders twice despite Strict mode being disabled

I have a specific component that renders twice. Basically, I am generating two random numbers and displaying them on the screen and for a split second, it shows undefined for both then shows the actual numbers. To test it further I did a simple console.log and it indeed logs it twice. So my question splits into two - 1. Why does the typography shows undefined for a split second before rendering? 2 - Why does it render twice?
Here's the related code:
Beginnging.js - this a counter that counts down from 3 and fires an RTK action, setting gameStart true.
function Beginning() {
const [count, setCount] = useState(3);
const [message, setMessage] = useState("");
const dispatch = useDispatch();
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
const handleCount = () => {
if (countRef.current === 1) {
return setMessage("GO");
}
return setCount((count) => count - 1);
};
useEffect(() => {
const interval = setInterval(() => {
handleCount();
}, 1000);
if (message==='GO') {
setTimeout(() => {
dispatch(start());
}, 1000);
}
return () => clearInterval(interval);
}, [count, message]);
return (
<>
<Typography variant="h1" fontStyle={'Poppins'} fontSize={36}>GET READY...</Typography>
<Typography fontSize={48} >{count}</Typography>
<Typography fontSize={60} >{message}</Typography>
</>
);
}
export default Beginning;
AdditionMain.js - based on gameStart, this is where I render Beginning.js, once it counts down, MainInput is rendered.
const AdditionMain = () => {
const gameStart = useSelector((state) => state.game.isStarted);
const operation = "+";
const calculation = generateNumbersAndResults().addition;
if (!gameStart){
return <Beginning/>
}
return (
<>
<MainInput operation={operation} calculation={calculation} />
</>
);
};
export default AdditionMain;
MainInput.js - the component in question.
const MainInput = ({ operation, calculation }) => {
const [enteredValue, setEnteredValue] = useState("");
const [correctValue, setCorrectValue] = useState(false);
const [calculatedNums, setCalculatedNums] = useState({});
const [isIncorrect, setIsIncorrect] = useState(false);
const [generateNewNumbers, setGenerateNewNumbers] = useState(false);
const [haveToEnterAnswer, setHaveToEnterAnswer] = useState(false);
const dispatch = useDispatch();
const seconds = useSelector((state) => state.game.seconds);
const points = useSelector((state) => state.game.points);
const lives = useSelector((state) => state.game.lives);
const timerValid = seconds > 0;
const newChallenge = () => {
setIsIncorrect(false);
setHaveToEnterAnswer(false);
dispatch(restart());
};
const handleCount = () => {
dispatch(loseTime());
};
useEffect(() => {
let interval;
if (timerValid) {
interval = setInterval(() => {
handleCount();
}, 1000);
}
return () => {
console.log("first");
clearInterval(interval);
};
}, [timerValid]);
useEffect(() => {
setCalculatedNums(calculation());
setGenerateNewNumbers(false);
setCorrectValue(false);
setEnteredValue("");
}, [generateNewNumbers]);
const submitHandler = () => {
if (correctValue) {
setGenerateNewNumbers(true);
dispatch(gainPoints());
dispatch(gainTime());
}
if (+enteredValue === calculatedNums.result) {
setCorrectValue(true);
} else if (enteredValue.length === 0) {
setHaveToEnterAnswer(true);
} else {
setIsIncorrect(true);
dispatch(loseLife());
}
};
const inputValueHandler = (value) => {
setIsIncorrect(false);
setHaveToEnterAnswer(false);
setEnteredValue(value);
};
const submitOrTryNewOne = () => {
return correctValue ? "Try new one" : "Submit";
};
return (
<>
<Navbar />
{console.log("hello")}
{seconds && lives > 0 ? (
<>
<GameInfo></GameInfo>
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography>
Fill in the box to make the equation true.
</Typography>
<Typography fontSize={28}>
{operation !== "/"
? `${calculatedNums.number1} ${operation} ${calculatedNums.number2}`
: `${calculatedNums.number2} ${operation} ${calculatedNums.number1}`}{" "}
=
</Typography>
<TextField
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
type="number"
name="sum"
id="outlined-basic"
label=""
variant="outlined"
onChange={(event) => {
inputValueHandler(event.target.value);
}}
disabled={correctValue}
value={enteredValue}
></TextField>
{haveToEnterAnswer && enterAnswer}
{correctValue && correctAnswer}
{isIncorrect && wrongAnswer}
<Button
type="button"
sx={{ marginTop: 1 }}
onClick={() => submitHandler()}
variant="outlined"
>
{isIncorrect ? "Try again!" : submitOrTryNewOne()}
</Button>
</Box>
</Container>
</>
) : (
<>
<Typography>GAME OVER</Typography>
<Typography>Final Score: {points}</Typography>
<Button onClick={newChallenge}>New Challenge</Button>
</>
)}
</>
);
};
export default MainInput;
PS: I'm trying to figure out how to get this running on Codesandbox
The problem is that you render the page before the calculaed nums are established.
Try this instead. It will prevent the element from displaying until nums are generated.
const [calculatedNums, setCalculatedNums] = useState(null);
// This is down in your render area. Only render once calculatedNums are non-null.
{ calculatedNums &&
<Typography fontSize={28}>
operation !== "/"
? `${calculatedNums.number1} ${operation} ${calculatedNums.number2}`
: `${calculatedNums.number2} ${operation} ${calculatedNums.number1}`}{" "}
=
</Typography>
}
In addition, you are probably generating your numbers twice, because you will generate new numbers on initial load, but then when you hit setGenerateNewNumbers(true); it will trigger a new calculation, which will then set the generatedNewNumbers to false, which will call the calc again, since it triggers whenever that state changes. It stops after that because it tries to set itself to false again and doesn't change.
You are changing a dependency value within the hook itself, causing it to run again. Without looking a lot more into your program flow, a really hacky way of fixing that would just be to wrap your useEffect operation inside an if check:
useEffect(()=>{
if (generateNewNumbers === true) {
//All your stuff here.
}
}, [generateNewNumbers]
That way, it won't run again when you set it to false.

Why is prior useEffect() still running for a significant amount of time even after the component is rendered?

I'm implementing stopwatch in ReactJs this is how my code looks as of now:
const App: React.FC = () => {
const [seconds, setSeconds] = useState(0);
const [isPaused, setIsPaused] = useState(false);
const secondsToTimerFormat = (seconds: number): string => {
console.log(seconds)
return (seconds-seconds%60)/60+":"+seconds%60
}
const manipulateTimer = (toPauseTimer: boolean) => {
setIsPaused(toPauseTimer);
}
useEffect(() => {
if(!isPaused){
setTimeout(() => {
setSeconds(seconds + 1)
}, 1000)
}
}, [seconds, isPaused])
return (
<div className="App">
{secondsToTimerFormat(seconds)}
<div>
<button onClick={() => {manipulateTimer(true)}}>Pause</button>
<button onClick={() => {manipulateTimer(false)}}>Resume</button>
<button onClick={() => {
setSeconds(0);
}}>Reset</button>
</div>
</div>
);
}
I'm expecting this to work normally. But the "Reset" button is not working as expected.
If I click on "Reset" after 13 seconds, this is the console.log() output.
If I add a new variable inside useEffect(), say something like let execute: boolean = true and then set it to false in useEffect() clean up, everything is working as expected.
So, I know the fix, but I want to know the reason behind the current behaviour. I understand that when I click on reset, there is already a useEffect() running with seconds value as 13. But since its setTimeout() ends in one second and at the same time, I'm doing setSeconds(0), why would the previous useEffect() run multiple times before coming to halt?
Issues like this usually arise because the timers being used are not being cleared between renders. Also, when the next state depends on the current state, it is better to use the second form of the state setter function which takes the current state as the parameter and returns the next state. Modify the useEffect as given below to get this to work:
useEffect(() => {
let timer;
if (!isPaused) {
timer = setTimeout(() => {
setSeconds((seconds) => seconds + 1);
}, 1000);
}
return () => {
if (timer) clearTimeout(timer);
};
}, [seconds, isPaused]);
Try using setInterval and separate methods for handling the timer state:
import { useState } from "react";
export default function App() {
const [seconds, setSeconds] = useState(0);
const [intervalId, setIntervalId] = useState(0);
const secondsToTimerFormat = (seconds) => {
console.log(seconds);
return (seconds - (seconds % 60)) / 60 + ":" + (seconds % 60);
};
const handleStart = () => {
const id = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
setIntervalId(id);
};
const handlePause = () => {
clearInterval(intervalId);
};
const handleReset = () => {
handlePause();
setSeconds(0);
};
return (
<div className="App">
{secondsToTimerFormat(seconds)}
<div>
<button
onClick={() => {
handlePause();
}}
>
Pause
</button>
<button
onClick={() => {
handleStart();
}}
>
Resume
</button>
<button
onClick={() => {
handleReset();
}}
>
Reset
</button>
</div>
</div>
);
}
Link to sandbox

How can I start / stop setInterval?

I've tried different ways, but It doesn't works.
[...]
const [automatic, setAutomatic] = useState(false);
[...]
var startAuto;
useEffect(() => {
if (!automatic) {
console.log("stop");
clearInterval(startAuto);
} else {
startAuto = setInterval(() => {
changeQuestion("+");
}, 5 * 1000);
}
}, [automatic]);
[...]
<Button
onPress={() => setAutomatic(!automatic)}
title="turn on/off"
/>
[...]
It works when I put a setTimeout outside the useEffect, that way:
setTimeout(() => { clearInterval(startAuto); alert('stop'); }, 10000);
But I want to have a button to start / stop
Your var startAuto; is redeclared on each render, and since changing the state causes a re-render, it never holds the reference to the interval, which is never cleared.
Use the useEffect cleanup function to clear the interval. Whenever automatic changes, it would call the cleanup (if returned by the previous invocation), and if automatic is true it would create a new interval loop, and return a new cleanup function of the current interval.
useEffect(() => {
if(!automatic) return;
const startAuto = setInterval(() => {
changeQuestion("+");
}, 5 * 1000);
return () => {
clearInterval(startAuto);
};
}, [automatic]);
Working example:
const { useState, useEffect } = React;
const Demo = () => {
const [automatic, setAutomatic] = useState(false);
const [question, changeQuestion] = useState(0);
useEffect(() => {
if(!automatic) return;
const startAuto = setInterval(() => {
changeQuestion(q => q + 1);
}, 5 * 100);
return () => {
clearInterval(startAuto);
};
}, [automatic]);
return (
<div>
<button
onClick={() => setAutomatic(!automatic)}
>
turn {automatic ? 'off' : 'on'}
</button>
<p>{question}</p>
</div>
);
}
ReactDOM
.createRoot(root)
.render(<Demo />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
For example, you can check and use this hook:
https://usehooks-ts.com/react-hook/use-interval
export default function Component() {
// The counter
const [count, setCount] = useState<number>(0)
// Dynamic delay
const [delay, setDelay] = useState<number>(1000)
// ON/OFF
const [isPlaying, setPlaying] = useState<boolean>(false)
useInterval(
() => {
// Your custom logic here
setCount(count + 1)
},
// Delay in milliseconds or null to stop it
isPlaying ? delay : null,
)
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setDelay(Number(event.target.value))
}
return (
<>
<h1>{count}</h1>
<button onClick={() => setPlaying(!isPlaying)}>
{isPlaying ? 'pause' : 'play'}
</button>
<p>
<label htmlFor="delay">Delay: </label>
<input
type="number"
name="delay"
onChange={handleChange}
value={delay}
/>
</p>
</>
)
}

useEffect is trigged unexpectedly

The useEffect hook is getting called unexpectedly. Once the timer reaches zero and when I set it to 10 (using the resend Otp button), then the timer starts to decrease faster than 1 second.
My component is as follows:
const EnterOtpView = ({ touched, errors, isSubmitting }) => {
const [timer, setTimer] = useState(3);
useEffect(() => {
if (timer > 0) {
setInterval(() => setTimer((prevTimer) => prevTimer - 1), 1000);
}
}, [timer]);
return (
<div>
{timer > 0 ? (
<p>Resend Otp in: {timer} seconds</p>
) : (
<button
type='button'
className='btn btn-link'
onClick={() => setTimer(10)}
>
resend otp
</button>
)}
</div>
);
};
What I have tried:
removed the timer from the dependencies array of useEffect
create a separate function to reduce timer
Any help will be appreciated. đŸ˜‡
Here is a working example of how you could set an interval once, prevent it from going negative, and clear it on unmount:
const {useState, useEffect} = React;
const Example = () => {
const [timer, setTimer] = useState(3);
useEffect(() => {
// setInterval returns the id of the interval so we can clear it later
const id = setInterval(() => {
setTimer((prevTimer) => {
// Move our negative logic inside the functional update
if (prevTimer > 0) {
return prevTimer - 1;
}
return 0;
})}, 1000);
// Clear the interval on umount
return () => {
clearInterval(id);
}
}, []);
return (
<div>
{timer > 0 ? (
<p>Resend Otp in: {timer} seconds</p>
) : (
<button
type='button'
className='btn btn-link'
onClick={() => setTimer(10)}
>
resend otp
</button>
)}
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('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="root"></div>
The solution that worked for me:
useEffect(() => {
setInterval(() => setTimer((prevTimer) => prevTimer - 1), 1000);
}, []);
I just changed the useEffect as above, i.e. made it into the componentDidMount form of useEffect and now its working fine.
Remove "timer" dependency from useEffect..
It will call everytime this function whenever timee

Use setinterval in React

I'm trying to use setInterval in React but stuck on something I don't properly understand.
The code is:
const Countdown = () => {
const [countdownSecond, setCountdownSecond] = React.useState(0);
function x() {
console.log(countdownSecond);
setCountdownSecond(countdownSecond + 1);
}
return (
<>
<button onClick={() => setInterval(x, 1000)}>Start</button>
{countdownSecond}
</>
);
}
The issue is that console always logs to 0. I'm not sure why is that. What concept am I misunderstanding?
You can do:
const Countdown = () => {
const [countdownSecond, setCountdownSecond] = React.useState(0);
const [start, setStart] = React.useState(false);
React.useEffect(()=>{
const interval = null
if (start) {
interval = setInterval(()=>{
setCountdownSecond(countdownSecond + 1);
}, 1000);
}
return ()=>{if (interval !== null) clearInterval(interval)};
},[start]);
function x(e) {
e.preventDefault();
console.log(countdownSecond);
setStart(!start);
}
return (
<>
<button onClick={(e) => x(e)}>{start?"Stop":"Start"}</button>
<p>Countdown: {countdownSecond}</p>
</>
);
}

Categories