The Timer functionality in react quiz application not resetting - javascript

I am trying to create a quiz application in react with timer. I have a timer component as shown in code:
import React from 'react'
import { useState } from 'react';
const CountDownTimer = ({hoursMinSecs,stopper}) => {
const { hours = 0, minutes = 0, seconds = 60 } = hoursMinSecs;
const [[hrs, mins, secs], setTime] = useState([hours, minutes, seconds]);
const [isActive, setIsActive] = useState(true);
function toggle() {
setIsActive(!isActive);
}
const tick = () => {
if (hrs === 0 && mins === 0 && secs === 0)
alert("TimeUP");
else if (mins === 0 && secs === 0) {
setTime([hrs - 1, 59, 59]);
} else if (secs === 0) {
setTime([hrs, mins - 1, 59]);
} else {
setTime([hrs, mins, secs - 1]);
}
};
const reset = () => setTime([parseInt(hours), parseInt(minutes), parseInt(seconds)]);
React.useEffect(() => {
let timerId=null;
if(isActive){
timerId = setInterval(() => tick(), 1000);
}
if(stopper==true){
clearInterval(timerId);
}
else if(stopper=="reset"){
setIsActive(true);
reset();
}
return () => clearInterval(timerId);
});
return (
<div>
<p>{`${hrs.toString().padStart(2, '0')}:${mins
.toString()
.padStart(2, '0')}:${secs.toString().padStart(2, '0')}`}</p>
</div>
);
}
export default CountDownTimer;
99% of code is taken from here Timer component.
Now I wanted to use this component in my quiz application which is basically fetching json data from an API call and sending it to a component. Sorry but the code is very unclean and cluttered.
import React from 'react'
import axios from 'axios'
import CountDownTimer from './CountDownTimer'
import { useEffect,useState } from 'react'
import Question from './Question'
const Quiz = () => {
const [hoursMinSecs,sethoursMinSecs] = useState({hours:0, minutes: 0, seconds: 10});
const [questions,setQuestions] = useState({q:{},idx:0,max:0});
const [hide,setHide] = useState(false);
const [stop,setStop] = useState(false);
const changeStop = (dat)=>{
setStop(dat);
var curr = {...questions};
curr.idx +=1;
setQuestions(curr);
setStop(false);
}
console.log("Array size is"+questions.max);
useEffect(()=>{
var config = {
method: 'get',
url: 'http://localhost:1337/Quizzes/617da24c8d757b423e0ae9a8',
headers: { }
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
setQuestions({q:response.data.QA,idx:0,max:response.data.QA.length});
})
.catch(function (error) {
console.log(error);
});
},[])
return (
<>
<div style={{display:hide ? "none":"block"}}>
<Question data={questions.q[questions.idx]} type="mcq" changer = {changeStop} />
</div>
<div>
<CountDownTimer hoursMinSecs={hoursMinSecs} stopper = {stop} />
</div>
</>
)
}
export default Quiz
Here is my question component
import React from 'react'
import { useState } from 'react';
const Question = (props) => {
const [ans,setans] = useState(0);
const changeOption = (e)=>{
console.log(e.target.value);
setans(e.target.value);
}
let ques;
let options;
let arr=[];
const check = ()=>{
props.changer(true);
if(ans==props.data.A){
alert("Hooray Correct")
}
else {alert("Wrong")}
}
console.log("Props data is"+JSON.stringify(props));
switch(props.type){
case "mcq":
ques = (<div>
Question: {props?.data?.Q}
</div>)
for(var key in props?.data?.O ){
arr.push({o:key,val:props?.data?.O[key]});
}
options = arr.map((e)=>{
return (<>{e.val}<input type="radio" value={e.o} name="abc"/><br/></>)
})
}
return (
<div>
{ques}
<div onChange={changeOption}>
{options}
</div>
<button onClick={check}>Check</button>
</div>
)
}
export default Question
Leave the logic for switch state and how and why I am displaying question in this way. What I want to achieve is that user will see a question and a 10 second timer will start. User will click on the answer and click next when timer will stop and user will be able to go to next question and again the timer will start with same time for example 10 second . But instead what is happening is that user clicks on answer,timer stops, gets to see if his answer is correct or not. The next question begins but the timer starts with leftover time. I want the time to reset but I am unable to figure out how to do it.The time should start again for the next question, instead it is being continued from there.
These two images will make you the Idea clear

Related

When I click on the exit button so that the timer stops. How to do that?

LabPart.jsx - Here, I want, when I click on the exit button, it will redirect me to the login page, and the timer will stop there, and when I log in from the login page, the timer will start from where it stopped. how do I do it. Here I have called clearInterval() to stop the timer, but it is not working. And after logging in, I can't even run it. please help me.
LabPart.jsx -
import React, {useState, useEffect} from 'react';
import Login from './Login';
const LabPart = ({Time , password}) => {
const [pass, setPass] = useState(password);
const [timeRemaining, setTimeRemaining] = useState(Time);
let stop = timeRemaining;
let interval = null;
useEffect(() => {
interval = setInterval(() => {
setTimeRemaining((time) => time - 1);
}, 1000);
return () => clearInterval(interval);
}, []);
const handleExitClick = () => {
console.log("logout successful");
clearInterval(interval);
setPass("");
};
// console.log(timeRemaining);
const hours = Math.floor(timeRemaining / 3600);
const minutes = Math.floor((timeRemaining % 3600) / 60);
const seconds = timeRemaining % 60;
return (
<>
{pass && timeRemaining>=0 ?
(<div>
<p>Time remaining: {hours}h {minutes}m {seconds}s</p>
<button onClick={handleExitClick}>Exit</button>
</div>)
: (<Login newTimeRemaining={stop} truee={true}/>)
}
</>
)
}
export default LabPart;
Login.jsx - After clicking the exit button, I want to show the time left on the login page.
import React, { useState, useEffect } from "react";
import LabPart from "./LabPart";
import '../style/login.css';
const Login = ({newTimeRemaining,truee} ) => {
const [password, setPassword] = useState("");
const [timeRemaining, setTimeRemaining] = useState(10);
const [openPage, setOpenPage] = useState(false);
// const [netTime, setNewtime] = useState(timeRemaining1);
const handlePasswordChange = (event) => {
setPassword(event.target.value);
};
const handleFormSubmit = (event) => {
event.preventDefault();
// setNewtime(timeRemaining1);
// if(newTimeRemaining<0)
// {
// setTimeRemaining(newTimeRemaining)
// }
if (password === "12345" ) {
console.log("login successful");
setOpenPage(true);
}
else {
alert("Incorrect Password")
}
};
return (
<>
{openPage ? <LabPart Time={10} password={password} />
:
<form onSubmit={handleFormSubmit}>
<input
type="password"
value={password}
onChange={handlePasswordChange}
/>
<button type="submit">Enter</button>
{
x ? newTimeRemaining : timeRemaining
}
</form>
}
</>
);
};
export default Login;
you should understand that react is very different (different from vue and angular) constructors will always callback when need to update without any cache create interval as a state
const [interval, setCInterval] = useState(null) //let interval = null;
useEffect(() => {
setCInterval(
setInterval(() => {
setTimeRemaining((time) => time - 1);
}, 1000)
)
return () => clearInterval(interval);
}, []);

React Context with notifications list [duplicate]

This question already has answers here:
Why React useState with functional update form is needed?
(5 answers)
Closed 6 months ago.
I am using a context to hold a list of all my notifications. When a notification is created, the context's state is set to a new list with this new notification added. The app re-renders, loops, and renders all the notifications to the screen. When a notification is due, the context is updated to filter out that notification.
However, the notifications have an animation so they can slide to the right before they are removed, which I made so it yields to update the notifications list until the animation is done. Unfortunately, if I close multiple notifications at once, some of them comeback because they are all simultaneously trying to update with an old list.
I don't know where to go.
Notification Context:
import { createContext, useContext, useState } from "react"
import Notification from "../components/Notification"
const NotificationsContext = createContext()
const SetNotificationsContext = createContext()
export function useNotifications() {
return useContext(NotificationsContext)
}
export function useSetNotifications() {
return useContext(SetNotificationsContext)
}
export default function NotificationsProvider({children}) {
const [notifications, setNotifications] = useState([])
return (
<NotificationsContext.Provider value={notifications}>
<SetNotificationsContext.Provider value={setNotifications}>
<div className="notifications-wrapper">
{notifications.map(note => {
return <Notification key={note.id} note={note}/>
})}
</div>
{children}
</SetNotificationsContext.Provider>
</NotificationsContext.Provider>
)
}
Notification Component:
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useNotifications, useSetNotifications } from '../contexts/NotificationsProvider'
import useInterval from '../hooks/useInterval'
function Notification({note}) {
const noteRef = useRef()
const notifications = useNotifications()
const setNotifications = useSetNotifications()
const [width, setWidth] = useState(0)
const [delay, setDelay] = useState(null)
// grow progress bar
useInterval(
useCallback(
() => setWidth(oldWidth => oldWidth + 1),
[setWidth]
),
delay)
// sliding left animation
useEffect(() => {
// console.log("slided left")
noteRef.current.onanimationend = () => {
noteRef.current.classList.remove("slide-left")
noteRef.current.onanimationend = undefined
}
noteRef.current.classList.add("slide-left")
}, [])
const handleStartTimer = useCallback(() => {
console.log("timer STARTED")
setDelay(10)
}, [setDelay])
const handlePauseTimer = useCallback(() => {
console.log("timer PAUSED")
setDelay(null)
}, [setDelay])
// filter off notification and sliding right animation
const handleCloseNotification = useCallback(() => {
console.log("slided right / removed notification")
handlePauseTimer()
noteRef.current.onanimationend = () => {
setNotifications([...notifications.filter(listNote => listNote.id !== note.id)])
}
noteRef.current.classList.add("slide-right")
}, [note.id, notifications, setNotifications, handlePauseTimer])
// notification is due
useLayoutEffect(() => {
if (width >= noteRef.current.clientWidth - 10) {
handleCloseNotification()
}
}, [width, handleCloseNotification, handlePauseTimer])
// start timer when notification is created
useEffect(() => {
handleStartTimer()
return handlePauseTimer
}, [handleStartTimer, handlePauseTimer])
return (
<div ref={noteRef} className={`notification-item ${note.type === "SUCCESS" ? "success" : "error"}`}
onMouseOver={handlePauseTimer} onMouseLeave={handleStartTimer}>
<button onClick={handleCloseNotification} className='closing-button'>✕</button>
<strong>{note.text}</strong>
<div className='notification-timer' style={{"width":`${width}px`}}></div>
</div>
)
}
export default Notification
Change your set notifications line to:
setNotifications(prevNotifications => [...prevNotifications.filter(listNote => listNote.id !== note.id)])
This will ensure you never use a stale value.

How can I improve performance setInterval per millisecond using by hooks in React?

I want to create timer with millisecond 3 digits. (like a stop watch)
So, I using setInterval with 1 millisecond.
For a while, (about 20sec) it was very lag. and make my computer so slowly.
then I should to refresh this page 😢
I using with useEffect with setInterval.
I think the problem is caused by re-render every millisecond.
But I do not know how to improve performance.
Here is my code (or code-sandbox https://codesandbox.io/s/peaceful-allen-gcdorn?file=/src/userTimer.js:0-603)
// App.js
import { useState } from "react";
import useTimer from "./userTimer";
export default function App() {
const [isStart, setIsStart] = useState(false);
const [timer, setTimer] = useState("0.000");
useTimer(isStart, setTimer);
return (
<div className="App">
<div style={{ fontSize: "4rem" }}>{timer}</div>
</div>
);
}
// userTimer.js
import { useEffect, useRef } from "react";
const useTimer = (isStart, setTimer) => {
const refId = useRef();
useEffect(() => {
if (isStart) {
const startTime = Date.now();
const id = setInterval(function () {
const elapsedTime = Date.now() - startTime;
const formatTime = (elapsedTime / 1000).toFixed(3);
setTimer(formatTime);
}, 1);
refId.current = id;
}
return () => clearInterval(refId.current);
}, [isStart, setTimer]);
const stopTimer = () => clearInterval(refId.current);
return { stopTimer };
};
export default useTimer;
NOTE: I should to setTimer and setIsStart in parent component(not in useTimer) because I want to pass that hook into other child component.

setTimout with pause/resume counter not updating on render

I would like to setup a counter which can be paused as well as resumed in React.js. But whatever I have tried so far is working the functionality part (pause/resume is working) but it's not updating the counter on render. Below is my code:
const ProgressBar = (props) => {
const [isPlay, setisPlay] = useState(false);
const [progressText, setProgressText] = useState(
props.duration ? props.duration : 20
);
var elapsed,
secondsLeft = 20;
const timer = () => {
// setInterval for every second
var countdown = setInterval(() => {
// if allowed time is used up, clear interval
if (secondsLeft < 0) {
clearInterval(countdown);
return;
}
// if paused, record elapsed time and return
if (isPlay === true) {
elapsed = secondsLeft;
return;
}
// decrement seconds left
secondsLeft--;
console.warn(secondsLeft);
}, 1000);
};
timer();
const stopProgress = () => {
setisPlay(!isPlay);
if (isPlay === false) {
secondsLeft = elapsed;
}
};
return (
<>
<p>{secondsLeft}</p>
</>
);
};
export default ProgressBar;
I have tried React.js state, global var type, global let type, react ref so far to make the variable global but none of them worked..
So basically why does your example not work?
Your secondsLeft variable not connected to your JSX. So each time your component rerendered it creates a new secondsLeft variable with a value of 20 (Because rerendering is simply the execution of your function that returns JSX)
How to make your variable values persist - useState or useReducer hook for react functional component or state for class based one. So react will store all the values for you for the next rerender cycle.
Second issue is React doesn't rerender your component, it just doesn't know when it should. So what causes rerendering of your component -
Props change
State change
Context change
adding/removing your component from the DOM
Maybe I missing some other cases
So example below works fine for me
import { useEffect, useState } from "react";
function App() {
const [pause, setPause] = useState(false);
const [secondsLeft, setSecondsLeft] = useState(20);
const timer = () => {
var countdown = setInterval(() => {
if (secondsLeft <= 0) {
clearInterval(countdown);
return;
}
if (pause === true) {
clearInterval(countdown);
return;
}
setSecondsLeft((sec) => sec - 1);
}, 1000);
return () => {
clearInterval(countdown);
};
};
useEffect(timer, [secondsLeft, pause]);
const pauseTimer = () => {
setPause((pause) => !pause);
};
return (
<div>
<span>Seconds Left</span>
<p>{secondsLeft}</p>
<button onClick={pauseTimer}>{pause ? "Start" : "Pause"}</button>
</div>
);
}
import React, { useState, useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
var timer = null;
function App() {
const [counter, setCounter] = useState(0);
const [isplayin, setIsPlaying] = useState(false);
const pause = () => {
setIsPlaying(false);
clearInterval(timer);
};
const reset = () => {
setIsPlaying(false);
setCounter(0);
clearInterval(timer);
};
const play = () => {
setIsPlaying(true);
timer = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
};
return (
<div className="App">
<p>Counter</p>
<h1>{counter}</h1>
{isplayin ? (
<>
<button onClick={() => pause()}>Pause</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<>
{counter > 0 ? (
<>
<button onClick={() => play()}>Resume</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<button onClick={() => play()}>Start</button>
)}
</>
)}
</div>
);
}
export default App;

How can I have an image that changes every 5 seconds in react.js?

import React, { useEffect, useState } from "react";
import aa from '../imgs/aa.png'
import aa2 from '../imgs/aa2.JPG'
import aa3 from '../imgs/aa3.JPG'
import aa4 from '../imgs/aa4.JPG'
import './AnimatedGalery.css'
export default function () {
return (
<div>
</div>
)
}
I have no idea how to start with this. I basically want an image (that I can resize and give css properties) that changes every 5 seconds to another one of those 4 imported images I have here.
The idea: put all imported images in a list. Have a state variable for the current image that should be displayed. Set an intervall that executes every 5 seconds in a useEffect that sets the new state with a randomly picked image.
const images = [aa, aa2, aa3, aa4];
export default function ImageSwapper() {
const [currentImage, setCurrentImage] = useState(null);
useEffect(() => {
const intervalId = setInterval(() => {
setCurrentImage(images[Math.floor(Math.random() * items.length)]);
}, 5000)
return () => clearInterval(intervalId);
}, [])
return (
<div>
<img src={currentImage} />
</div>
)
}
If you want to have a rotation of your images, then I would just save the currentIndex and display its image:
const images = [aa, aa2, aa3, aa4];
export default function ImageSwapper() {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
if(currentIndex === images.length - 1) {
setCurrentIndex(0);
}
else {
setCurrentIndex(currentIndex + 1);
}
}, 5000)
return () => clearInterval(intervalId);
}, [])
return (
<div>
<img src={images[currentIndex]} />
</div>
)
}
You can define a function to get current image, and use setInterval() to change image every five seconds, which you can define inside useEffect when the component renders:
const getPicture = index => {
switch (index) {
case 1:
return "'../imgs/aa.png";
case 2:
...
default:
break;
}
};
const NUMBER_OF_PICTURES = 4;
export default function Menu() {
const [index, setIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setIndex(prevIndex => (index == NUMBER_OF_PICTURES ? 0 : prevIndex + 1));
}, 5000);
return () => {
/* cleanup */
clearInterval(timer);
};
/* on component render*/
}, []);
return (
<>
<img src={getPicture(index)} />
</>
);
}
you can add all the images name in a array like this :-
const temp =[aa,aa2,aa3,aa4]
const [counter,setCounter] = useState(0);
setInterval(function(){
counter === 4 ? setCounter(0) : setCounter(counter + 1);
}, 5000);
In tag you can use it like this
<img src=`{temp[counter]}` />

Categories

Resources