React how to wait until state is updated without extra state variables? - reactjs

I cannot understand. To cut it short, I have these 2 variables which are being used in the state
questionLoaded
gameOver
This component needs to wait until it finishes getting data using the function selectRandomQuestion.
Therefore if i remove the questionLoaded, it will give me exception when it's looping through the map.
Now I had to create another variable to check when the game is over to unrender this component, therefore the terniary condition questionLoaded || gameOver ?
I tried checking the variable currentQuestion to conditionally render, but for some reason it will give exception when looping through the map in the options array of this property. Which means currentQuestion gets data first before it's own child property options has any data when hitting the condition check
The problem is that this looks kind of dirty, I'm still using react just for a few weeks and I'm not sure if this is the appropriate way to deal with state updates or is there a better way to handle it.
To sum it up, my problem is when rendering components, I often need to wait for the state to update when the rendering depends on some conditions, and everytime I need a new condition that means I need another set of logic using another state variable. Which means creating another useEffect with extra logic and to be triggered when that variable is updated.
import React, { useState, useEffect } from 'react';
import './GuessPicture.css';
import questions from './data/questions.js';
function GuessPicture() {
const [currentQuestion, setCurrentQuestion] = useState({});
const [unansweredQuestions, setUnansweredQuestions] = useState([]);
const [answeredQuestions, setAnsweredQuestions] = useState([]);
const [questionLoaded, setQuestionLoaded] = useState(false);
const [gameOver, setGameOver] = useState(false);
useEffect(() => {
setUnansweredQuestions(questions);
selectRandomQuestion();
setQuestionLoaded(true);
}, []);
useEffect(() => {
if (unansweredQuestions.length > 0) nextQuestion();
else alert('game over');
}, [unansweredQuestions]);
function selectRandomQuestion() {
const index = Math.floor(Math.random() * questions.length);
let selectedQuestion = questions[index];
selectedQuestion.options = shuffle(selectedQuestion.options);
setCurrentQuestion(selectedQuestion);
}
function nextQuestion() {
const index = Math.floor(Math.random() * unansweredQuestions.length);
let selectedQuestion = unansweredQuestions[index];
selectedQuestion.options = shuffle(selectedQuestion.options);
setCurrentQuestion(selectedQuestion);
}
// Fisher Yates Shuffle Algorithm
function shuffle(array) {
var currentIndex = array.length,
temporaryValue,
randomIndex;
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// And swap it with the current element.
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
const onClickOption = (event) => {
if (currentQuestion.correctValue == event.target.dataset.value) {
setAnsweredQuestions((answeredQuestions) => [
...answeredQuestions,
currentQuestion,
]);
const newUnansweredQuestions = unansweredQuestions.filter(
(item) => item.id != currentQuestion.id
);
setUnansweredQuestions(newUnansweredQuestions);
} else alert('Wrong');
};
return (
<>
{questionLoaded || gameOver ? (
<div className="guess-picture-container">
<div className="guess-picture">
<img src={currentQuestion.image} alt="English 4 Fun" />
</div>
<div className="guess-picture-answers-grid">
{currentQuestion.options.map((key) => (
<button
key={key.value}
onClick={onClickOption}
data-value={key.value}
>
{key.display}
</button>
))}
</div>
</div>
) : null}
</>
);
}
export default GuessPicture;

Your code looks fine to me, however I would avoid the ternary operator in this situation and use the short-circuit operator instead. This makes your code a little cleaner.
instead of:
{questionLoaded || gameOver ? <SomeComponentHere /> : null}
try using:
{(questionLoaded || gameOver) && <SomeComponentHere />}
Why avoid the ternary operator? Because it's not a ternary operation, it is a boolean check. This makes your code more semantically appropriate.

Related

Pomodoro Timer REACTJS not working - useState not updating

I'm quite new to ReactJS and have been working on a Pomodoro Timer. How it works is that whenever the "Work Timer" reaches zero, it switches to the "Break Timer".
const [secondsLeft, setSecondsLeft] = useState(newTimer.work);
However,
this line: setSecondsLeft(nextSeconds); // This does not update secondsLeft
under useEffect, does not update the secondsLeft which results in the timer for the break to display wrongly.
Could use some advice to make this work.
Thanks!
import {
Button,
Heading,
VStack,
Stack,
HStack,
Box,
CircularProgress,
Text,
CircularProgressLabel,
} from '#chakra-ui/react';
import { useState, useRef, useContext, useEffect } from 'react';
import { SettingsContext } from '../helpers/SettingsContext';
import PomodoroSettings from './PomodoroSettings';
// Pomodoro
const Pomodoro = () => {
//Timer
const { newTimer, setNewTimer } = useContext(SettingsContext);
const [isPaused, setIsPaused] = useState(true);
const [secondsLeft, setSecondsLeft] = useState(newTimer.work);
const secondsLeftRef = useRef(secondsLeft);
const isPausedRef = useRef(isPaused);
const modeRef = useRef(newTimer.active);
function tick() {
secondsLeftRef.current--;
setSecondsLeft(secondsLeftRef.current);
}
useEffect(() => {
function switchMode() {
const nextMode = modeRef.current === 'work' ? 'break' : 'work';
const nextSeconds =
(nextMode === 'work' ? newTimer.work : newTimer.short) * 60;
setNewTimer({
work: newTimer.work,
short: newTimer.short,
long: newTimer.long,
active: nextMode,
});
modeRef.current = nextMode;
console.log(nextMode);
console.log('Next: ' + nextSeconds);
setSecondsLeft(nextSeconds); // This does not update secondsLeft
console.log('SecondsLeft: ' + secondsLeft);
secondsLeftRef.current = nextSeconds;
}
secondsLeftRef.current = newTimer.work * 60;
setSecondsLeft(secondsLeftRef.current);
const interval = setInterval(() => {
if (isPausedRef.current) {
return;
}
if (secondsLeftRef.current === 0) {
return switchMode();
}
tick();
}, 1000);
return () => clearInterval(interval);
}, [newTimer, setNewTimer]);
const totalSeconds =
newTimer.active === 'work' ? newTimer.work * 60 : newTimer.short * 60;
const percentage = Math.round((secondsLeft / totalSeconds) * 100);
const minutes = Math.floor(secondsLeft / 60);
let seconds = secondsLeft % 60;
if (seconds < 10) seconds = '0' + seconds;
// Start / Pause
const handleButton = (e) => {
e.preventDefault();
if (isPaused) {
setIsPaused(!isPaused);
isPausedRef.current = false;
} else {
setIsPaused(!isPaused);
isPausedRef.current = true;
}
};
// console.log(percentage)
// console.log(newTimer.active)
// console.log(secondsLeft)
// console.log(totalSeconds)
return (
<div>
<Heading as='h4' size='md'>
{' '}
Pomodoro{' '}
</Heading>
<VStack>
<HStack>
<Button variant='ghost'>Pomodoro</Button>
<Button variant='ghost'>Short Break</Button>
<Button variant='ghost'>Long Break</Button>
</HStack>
<CircularProgress
value={percentage}
color={newTimer.active === 'work' ? 'red.400' : 'green.400'}
size='200px'
thickness='10px'
>
<CircularProgressLabel>
<Text fontSize='3xl'>{minutes + ':' + seconds}</Text>
</CircularProgressLabel>
</CircularProgress>
<HStack>
<Button variant='outline' onClick={handleButton}>
{isPaused ? <div> START </div> : <div> STOP </div>}
</Button>
<PomodoroSettings />
</HStack>
</VStack>
</div>
);
};
export default Pomodoro;
useState uses array const [value, setValue] like this. It might be just an error in this declaration of newTimer.
There is too much going on in one eseEffect(). The initial value of newTimer.work is not modified so each re-render will set secondsLeftRef to the same initial value.
Since useEffect()updates a context object, the component will re-render on every iteration, setting secondsLeftRef to its initial value.
Not sure where the problem is but few things I noticed that you can try to fix or improve in your code -
Do not use or assign context or props value in useState as you are doing it for secondsLeft, reason is that useState is supposed to run once when component gets rendered first time and you might not get value for newTimer.work on very first render.
For this you can use useEffect and newTimer in dependency array.
const [secondsLeft, setSecondsLeft] = useState(newTimer.work);
You are trying to log secondsLeft immediatly after calling setSecondsLeft this will not give you correct value because setting state is asynchronous in react so it will not be available on next line after calling set state.
setSecondsLeft(nextSeconds);
console.log('SecondsLeft: ' + secondsLeft);
One last thing I noticed looking at your code is that it's possible that you might be registering multiple setInterval because below line is responsible for clearing interval only when component gets unmount but your useEffect will get called multiple times based on its dependencies.
return () => clearInterval(interval);
Try to debug your code for above 3rd point and if that's the problem you
can try clearing your interval in start of useEffect or add
some conditions to make sure it will get register once to get
expected results.
Note - I have not tried executing your code, but let me know if this helps or if I can improve my answer in any way.

UseEffect doesn't execute itself before rest of the code. Normal or not?

currently I am working on a project and find myself in a bothersome situation. Is it normal for the components to load before useEffect?
On my page I want to implement pagination on sidebar. I have state which will determine current page and that state which is number will be an index which will take nested array and show the content.
However data is an array without nested arrays and firstly I should convert that array into array with nested ones. Because that, I want to run that inside side effect because it should only be done at initial loading.
Now I am trying with hardcoded values and later will set dynamic ones.
The problem now is that useEffect doesn't run first and the rest of code actually executes itself before useEffect and I got errors like "MenuList.js:173 Uncaught TypeError: DUMMY_FOOD[0].map is not a function" and ect. I know that my array is not in right format hence if I log without this [0] it works.
What is problem?
Code:
const MenuList = () => {
const navigate = useNavigate();
const location = useLocation();
const params = useParams();
const [page, setPate] = useState(0);
useEffect(() => {
const pages = Math.ceil(DUMMY_FOOD.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
console.log("picapicapiac");
helpArr.push(DUMMY_FOOD[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
console.log(arr);
DUMMY_FOOD = arr;
}, []);
console.log(DUMMY_FOOD);
const queryPrams = new URLSearchParams(location.search);
const sort = queryPrams.get("sort");
const onNextPageHandler = () => {};
const onPreviousPageHandler = () => {};
const onSortPageHandler = () => {
navigate(`/menu/${params.foodId}/?sort=${sort === "asc" ? "desc" : "asc"}`);
sort === "asc"
? (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => a.foodPrice - b.foodPrice))
: (DUMMY_FOOD = DUMMY_FOOD.sort((a, b) => b.foodPrice - a.foodPrice));
};
return (
<Fragment>
<div className={classes["menu-list"]}>
{DUMMY_FOOD.map((foodObj) => (
<MenuItem key={foodObj.id} foodObj={foodObj} />
))}
</div>
<div className={classes["menu-list__buttons"]}>
<Button type="button" onClick={onPreviousPageHandler}>
Page 2
</Button>
<Button type="button" onClick={onSortPageHandler}>
{sort === "asc" ? `Descending &#8593` : `Ascending &#8595`}
</Button>
<Button type="button" onClick={onNextPageHandler}>
Page 3
</Button>
</div>
</Fragment>
);
};
export default MenuList;
This is how useEffect works. It is simply explained in the react docs:
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.
This is expected behavior, useEffect is not a synchronous function.
What you want to do is make sure your arrays have items in them and are not null/undefined, to make sure renders don't break.
From React's docs:
Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don’t need to happen synchronously.
Using your Dummy_food variable below.. not sure where it's coming from but I kept it in the code.
useEffect(() => {
const pages = Math.ceil(DUMMY_FOOD.length / 5);
const arr = [];
let helpArr = [];
let c = 0;
for (let i = 0; i < pages; i++) {
for (let j = c; j < c + 5; j++) {
console.log("picapicapiac");
helpArr.push(DUMMY_FOOD[j]);
}
c += 5;
arr.push(helpArr);
helpArr = [];
}
console.log(arr);
DUMMY_FOOD = arr;
}, []);
Have a portion that displays your links. This will not error out if there are no links at render.
const RenderPagination= () => {
return DUMMY_FOOD.map((f, index) => {
return (
<li>{f}</li>
);
});
};
Then where your displaying your items...
{RenderPagination()}

Rendering html in react with hooks not working

I have a Task component which shows what task i need to do and when, and i want this to auto update when its time for it to change. So i was trying to do something like this
function Task(){
const [data, setData] = useState();
const [currentTask, setTask] = useState();
React.useEffect(() => {
fetch("/api")
.then((res) => res.json())
.then((data) => setData(data.tasks));
}, []);
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
return(
<div>
{console.log("inside return")}
<p>{currentTask? currentTask: "Loading..."}</p>
</div>
)
}
when I log currentTask I get back the result I want. However, before that, there are a few logs of undefined, but at the end, it prints out the actual value of the task and then logs "inside return". But the only thing getting rendered on my app is "loading...", why is this? currentTask has a value, am I missing something? I've tried all that I could but I couldn't understand it.
Thank you for your help!
This is happening because the function determineTask() is being called before the data is fetched. Data fetching often takes a couple seconds, and the determineTask() function is being called as soon as the component renders.
I would suggest putting the block of code:
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
inside a useEffect. useEffect has a second parameter of a dependency array that will trigger the useEffect to run again when one of the dependencies change.
In your first useEffect, you're changing the data state variable and then you want the currentTask state variable to change once you've retrieved the data.
So, your second useEffect will look something like:
useEffect(() => {
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
},[data]);

State getting reset to 0 after render

I am rendering photos from unsplash api. And I am keeping the index of the photos to be used in the lightbox, after the initial render state of imageindex goes back to 0, how can I retain its value?
I will show some code
const ImageList = ({ image, isLoaded }) => {
const [imageIndex, setImageIndex] = useState(0);
const [isOpen, setIsOpen] = useState('false');
const onClickHandler = (e) => {
setIsOpen(true);
setImageIndex(e.target.id);
};
const imgs = image.map((img, index) => (
<img
id={index}
key={img.id}
src={img.urls.small}
onClick={onClickHandler}
if (isOpen === true) {
return (
<Lightbox
onCloseRequest={() => setIsOpen(false)}
mainSrc={image[imageIndex].urls.regular}
onMoveNextRequest={() => setImageIndex((imageIndex + 1) % image.length)}
onMovePrevRequest={() => setImageIndex((imageIndex + image.length - 1) % image.length)}
nextSrc={image[(imageIndex + 1) % image.length].urls.regular}
prevSrc={image[(imageIndex + image.length - 1) % image.length].urls.regular}
/>
after the initial render state, imageIndex goes back to 0.
That makes sense, the initial render would use whatever you set as the default value. You can use something like local storage to help you keep track of the index of the last used item. It's a bit primitive, but until you integrate something like Node/MongoDB for database collections, this will be perfect.
In your component, import useEffect() from React. This hook lets us execute some logic any time the state-index value changes, or anything else you might have in mind.
import React, { useEffect } from "react"
Then inside your component, define two useEffect() blocks.
Getting last used index from localStorage on intitial load:
useEffect(() => {
const lastIndex = localStorage.getItem("index", imageIndex)
setImageIndex(imageIndex)
}, []) //set as an empty array so it will only execute once.
Saving index to localStorage on change:
useEffect(() => {
localStorage.setItem("index", imageIndex)
}, [imageIndex]) //define values to subscribe to here. Will execute anytime value changes.

useState() is not updating state from event handler?

I'm trying to recreate an old flash game in React. The object of the game is to press a button down for a certain length of time.
This is the old game:
http://www.zefrank.com/everysecond/index.html
Here is my new React implementation:
https://codesandbox.io/s/github/inspectordanno/every_second
I'm running into a problem. When the mouse is released, I calculate the amount of time between when the button was pressed and when it was released, using the Moment.js time library. If the timeDifference between the onMouseDown and onMouseUp event is within the targetTime, I want the game level to increase and the targetTime to increase as well.
I'm implementing this logic in the handleMouseUp event handler. I'm getting the expected times printed to the screen, but the logic isn't working. In addition, when I console.log() the times, they are different than the ones being printed to the screen. I'm fairly certain timeHeld and timeDifference aren't being updated correctly.
Initially I thought there was a problem with the way I was doing the event handler and I need to use useRef() or useCallback(), but after browsing a few other questions I don't understand these well enough to know if I have to use them in this situation. Since I don't need access to the previous state, I don't think I need to use them, right?
The game logic is in this wrapper component:
import React, { useState } from 'react';
import moment from 'moment';
import Button from './Button';
import Level from './Level';
import TargetTime from './TargetTime';
import TimeIndicator from './TimeIndicator';
import Tries from './Tries';
const TimerApp = () => {
const [level, setLevel] = useState(1);
const [targetTime, setTargetTime] = useState(.2);
const [isPressed, setIsPressed] = useState(false);
const [whenPressed, setPressed] = useState(moment());
const [whenReleased, setReleased] = useState(moment());
const [tries, setTries] = useState(3);
const [gameStarted, setGameStarted] = useState(false);
const [gameOver, setGameOver] = useState(false);
const timeHeld = whenReleased.diff(whenPressed) / 1000;
let timeDifference = Math.abs(targetTime - timeHeld);
timeDifference = Math.round(1000 * timeDifference) / 1000; //rounded
const handleMouseDown = () => {
!gameStarted && setGameStarted(true); //initialize game on the first click
setIsPressed(true);
setPressed(moment());
};
const handleMouseUp = () => {
setIsPressed(false);
setReleased(moment());
console.log(timeHeld);
console.log(timeDifference);
if (timeDifference <= .1) {
setLevel(level + 1);
setTargetTime(targetTime + .2);
} else if (timeDifference > .1 && tries >= 1) {
setTries(tries - 1);
}
if (tries === 1) {
setGameOver(true);
}
};
return (
<div>
<Level level={level}/>
<TargetTime targetTime={targetTime} />
<Button handleMouseDown={handleMouseDown} handleMouseUp={handleMouseUp} isGameOver={gameOver} />
<TimeIndicator timeHeld={timeHeld} timeDifference={timeDifference} isPressed={isPressed} gameStarted={gameStarted} />
<Tries tries={tries} />
{gameOver && <h1>Game Over!</h1>}
</div>
)
}
export default TimerApp;
If you want to check the whole app please refer to the sandbox.
If you update some state inside a function, and then try to use that state in the same function, it will not use the updated values. Functions snapshots the values of state when function is called and uses that throughout the function. This was not a case in class component's this.setState, but this is the case in hooks. this.setState also doesn't updates the values eagerly, but it can update while in the same function depending on a few things(which I am not qualified enough to explain).
To use updated values you need a ref. Hence use a useRef hook. [docs]
I have fixed you code you can see it here: https://codesandbox.io/s/everysecond-4uqvv?fontsize=14
It can be written in a better way but that you will have to do yourself.
Adding code in answer too for completion(with some comments to explain stuff, and suggest improvements):
import React, { useRef, useState } from "react";
import moment from "moment";
import Button from "./Button";
import Level from "./Level";
import TargetTime from "./TargetTime";
import TimeIndicator from "./TimeIndicator";
import Tries from "./Tries";
const TimerApp = () => {
const [level, setLevel] = useState(1);
const [targetTime, setTargetTime] = useState(0.2);
const [isPressed, setIsPressed] = useState(false);
const whenPressed = useRef(moment());
const whenReleased = useRef(moment());
const [tries, setTries] = useState(3);
const [gameStarted, setGameStarted] = useState(false);
const [gameOver, setGameOver] = useState(false);
const timeHeld = useRef(null); // make it a ref instead of just a variable
const timeDifference = useRef(null); // make it a ref instead of just a variable
const handleMouseDown = () => {
!gameStarted && setGameStarted(true); //initialize game on the first click
setIsPressed(true);
whenPressed.current = moment();
};
const handleMouseUp = () => {
setIsPressed(false);
whenReleased.current = moment();
timeHeld.current = whenReleased.current.diff(whenPressed.current) / 1000;
timeDifference.current = Math.abs(targetTime - timeHeld.current);
timeDifference.current = Math.round(1000 * timeDifference.current) / 1000; //rounded
console.log(timeHeld.current);
console.log(timeDifference.current);
if (timeDifference.current <= 0.1) {
setLevel(level + 1);
setTargetTime(targetTime + 0.2);
} else if (timeDifference.current > 0.1 && tries >= 1) {
setTries(tries - 1);
// consider using ref for tries as well to get rid of this weird tries === 1 and use tries.current === 0
if (tries === 1) {
setGameOver(true);
}
}
};
return (
<div>
<Level level={level} />
<TargetTime targetTime={targetTime} />
<Button
handleMouseDown={handleMouseDown}
handleMouseUp={handleMouseUp}
isGameOver={gameOver}
/>
<TimeIndicator
timeHeld={timeHeld.current}
timeDifference={timeDifference.current}
isPressed={isPressed}
gameStarted={gameStarted}
/>
<Tries tries={tries} />
{gameOver && <h1>Game Over!</h1>}
</div>
);
};
export default TimerApp;
PS: Don't use unnecessary third party libraries, especially big ones like MomentJs. They increase your bundle size significantly. Use can easily get current timestamp using vanilla js. Date.now() will give you current unix timestamp, you can subtract two timestamps to get the duration in ms.
Also you have some unnecessary state like gameOver, you can just check if tries > 0 to decide gameOver.
Similarly instead of targetTime you can just use level * .2, no need to additional state.
Also whenReleased doesn't needs to be a ref or state, it can be just a local variable in mouseup handler.
State updaters can take a value indicating the new state, or a function that maps the current state to a new state. The latter is the right tool for the job when you have state that depends on mutations of itself.
This should work if you update places in the code where you use the pattern
[value, setValue ] = useState(initial);
...
setValue(value + change);
to
[value, setValue ] = useState(initial);
...
setValue((curValue) => curValue + change);
For example,
if (timeDifference <= .1) {
setLevel((curLevel) => curLevel + 1);
setTargetTime((curTarget) => curTarget + .2);
} else if (timeDifference > .1 && tries >= 1) {
setTries((curTries) => {
const newTries = curTries - 1;
if (newTries === 1) {
setGameOver(true);
}
return newTries;
});
}
I think there are two subtle things going on here:
When you call a setState method (e.g. setRelease(moment())) the value of the associated variable (e.g. whenReleased) does not update immediately. Instead it queues a re-render, and only once that render happens will the value be updated.
The event handlers (e.g. handleMouseUp) are closures. Meaning they capture the values from the parent scope. And again therefor are only updated on a re-render. So, when handleMouseUp runs, timeDifference (and timeHeld) will be the value that was calculated during the last render.
The changes you therefore need to make are:
Move the calculation of timeDifference inside your handleMouseUp event handler.
Instead of using whenReleased in your timeDifference calculation, you need to use a local variable set to moment() (You can also set whenReleased via setReleased, but that value won't be available to you inside your event handler).
const handleMouseUp = () => {
const released = moment();
setIsPressed(false);
setReleased(released);
const timeHeld = released.diff(whenPressed) / 1000;
const timeDifference = Math.round(1000 * Math.abs(targetTime - timeHeld)) / 1000;
console.log(timeHeld);
console.log(timeDifference);
if (timeDifference <= .1) {
setLevel(level + 1);
setTargetTime(targetTime + .2);
} else if (timeDifference > .1 && tries >= 1) {
setTries(tries - 1);
}
if (tries === 1) {
setGameOver(true);
}
};
When the mouse is released, I calculate the amount of time between when the button was pressed and when it was released...
This is not true ... but can be ... just move the time difference calulations into handleMouseUp()
... also - you don't need whenReleased

Resources