I'm working on a React app and am stuck on two key features:
- After time has passed and there hasn't been any clicks, start subtracting points
- preserve a "total score" amount and keep adding to it, but reseting a "score" field to one every new game.
The code looks like this:
import React, { useEffect, useState } from "react";
function ScoreCard({ setResetGame }) {
const [strokeScore, setStrokeScore] = useState(1);
const [totalStrokeScore, setTotalStrokeScore] = useState(1);
const [strokeCountdown, setStrokeCountdown] = useState();
const [strokeCountdownSpeedWord, setStrokeCountdownSpeedWord] = useState();
const endOfGameRound = () => {
document.getElementById("stroke-counter-button").disabled = true;
};
const addToStrokeScore = () => {
setStrokeScore(strokeScore + 1);
if (strokeCountdown === 0) {
endOfGameRound();
}
};
const countDown = () => {
// let strokeCountdown = Math.floor(Math.random() * 31) + 100;
let strokeCountdown = 20;
let strokeCountdownSpeedOptions = [1000, 500, 300, 200];
let strokeCountDownSpeed =
strokeCountdownSpeedOptions[
Math.floor(Math.random() * strokeCountdownSpeedOptions.length)
];
if (strokeCountDownSpeed === 1000) {
setStrokeCountdownSpeedWord("SLOW");
} else if (strokeCountDownSpeed === 500) {
setStrokeCountdownSpeedWord(" MEDIUM");
} else if (strokeCountDownSpeed === 300) {
setStrokeCountdownSpeedWord("FAST");
} else {
setStrokeCountdownSpeedWord("SUPER FAST");
}
document.getElementById("stroke-counter-button").disabled = false;
let strokeCounter = setInterval(() => {
strokeCountdown--;
setStrokeCountdown(strokeCountdown);
if (strokeCountdown === 0) {
endOfGameRound();
clearInterval(strokeCounter);
}
}, strokeCountDownSpeed);
};
const restartGame = () => {
setStrokeScore(1);
};
useEffect(() => {
if (strokeScore === 1) {
countDown();
}
setTotalStrokeScore(strokeScore);
// eslint-disable-next-line
}, [strokeScore]);
return (
<div className="game__score-card">
<div className="game__speed-level">
Speed: {strokeCountdownSpeedWord}
</div>
<div className="game__stroke-countdown">
Countdown: {strokeCountdown}
</div>
<p>Score: {strokeScore}</p>
<button id="stroke-counter-button" onClick={addToStrokeScore}>
{strokeCountdown === 0 ? "Game Over" : "Stroke"}
</button>
{strokeCountdown === 0 ? (
<button onClick={restartGame}>Play Again</button>
) : (
<button disabled>Game in Progress</button>
)}
<div className="game__total-score">
Total score: {totalStrokeScore}
</div>
</div>
);
}
export default ScoreCard;
A working example can be seen here: https://codesandbox.io/s/festive-wave-o0c2s?file=/src/App.js:0-2665
Issue #1
Let's attempt 1 - I basically would like to preserve the "total score" with every new round (in const [totalStrokeScore, setTotalStrokeScore] = useState(1); ) - my understanding of react is everything is state, so I would like to update the state of the totalStrokeScore with the active strokeScore - that block looks like this.
const restartGame = () => {
setStrokeScore(1);
setResetGame(prev => prev + 1);
}
useEffect(() => {
if (strokeScore === 1) {
countDown();
}
setTotalStrokeScore(strokeScore);
// eslint-disable-next-line
}, [strokeScore]);
What I thought would work is if I change it to this, so that it gets the active state number in the state for the score and add it to the total score, so this:
const restartGame = () => {
// set the total score with the score first
setTotalStrokeScore(strokeScore);
// then once that's done,
setStrokeScore(1);
// this is an attempt to remount another component by modifing state since thats I guess how you remount components
// but I'll be asking that question when this one is resolved
setResetGame(prev => prev + 1);
}
useEffect(() => {
// only if the score is 1 do we kick off countdown
if (strokeScore === 1) {
countDown();
}
// set the score during play
setTotalStrokeScore(strokeScore);
// eslint-disable-next-line
}, [strokeScore]);
What happens is even leading with setTotalStrokeScore(strokeScore); it's 1, why?
I want to have it so the total score stays and with every round, the score resets but that score gets added to Total Score. How do I do this?
I think this is because it has something to do with synchronous vs async but idk.
Is there a synchronous alternative of setState() in Reactjs
I guess it doesn't exist doing like that so idk.
How to update state using setState based on another state after rendering?
React docs helps you:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
And also gives you the solution:
Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
So, ensure that you don't use the state, right after updating the state.
How do I change the above code to do it synchoronous? So that I can take what the score is, add it to total score, and preserve it through out the rounds? Is state the not right way to do this? Do I need to think about async? Does props need to be involved? Do I need to nest components?
Issue #2:
I would like to have it so that if you haven't clicked "Stroke" in a while (say 4 seconds) points start getting taken off.
My thought process/logic to solve that is this:
• Have a state that says wether button has been clicked recently or not by going const [strokeWasClicked, setStrokeWasClicked] = useState(false)
• Have a state setter (or whatever it's called) to minus a point (setStrokeScore(strokeScore - 1);)
• On click, set it to true
• After a second or so reset it BACK to false, and in the setInterval of countDown() check for the status of strokeWasClicked is false do setStrokeScore(strokeScore - 1);
Is this thr right approach? Is there a better way?
That implementation looks like this:
const [strokeWasClicked, setStrokeWasClicked] = useState(false)
...
const addToStrokeScore = () => {
setStrokeScore(strokeScore + 1);
setStrokeWasClicked(true)
console.log(strokeWasClicked)
setTimeout(() => {
setStrokeWasClicked(false)
}, 2000)
if (strokeCountdown === 0) {
endOfGameRound()
}
console.log(strokeWasClicked)
}
...
const countDown = () => {
// let strokeCountdown = Math.floor(Math.random() * 31) + 100;
let strokeCountdown = 20
let strokeCountdownSpeedOptions = [1000, 500, 300, 200];
let strokeCountDownSpeed = strokeCountdownSpeedOptions[Math.floor(Math.random()*strokeCountdownSpeedOptions.length)];
if (strokeCountDownSpeed === 1000) {
setStrokeCountdownSpeedWord('SLOW')
} else if (strokeCountDownSpeed === 500) {
setStrokeCountdownSpeedWord(' MEDIUM')
} else if (strokeCountDownSpeed === 300) {
setStrokeCountdownSpeedWord('FAST')
} else {
setStrokeCountdownSpeedWord('SUPER FAST')
}
document.getElementById('stroke-counter-button').disabled = false;
let strokeCounter = setInterval(() => {
strokeCountdown--
setStrokeCountdown(strokeCountdown)
if (strokeCountdown === 0) {
endOfGameRound()
clearInterval(strokeCounter)
}
if (!strokeWasClicked) {
setStrokeScore(strokeScore - 1);
}
}, strokeCountDownSpeed)
}
...
This isn't working because the timer is very erratic and all over the place.
For some reason now, when I click on "stroke" it calls countDown() again...but I don't reference that function in my click handler, so why is it calling it again?
how does useEffect work?
useEffect(() => {
// only if the score is 1 do we kick off countdown
if (strokeScore === 1) {
countDown();
}
// set the score during play
setTotalStrokeScore(strokeScore);
// eslint-disable-next-line
}, [strokeScore]);
I say here:
if score is and onnly is 1 and it s strictly an integer (===) then kick off the countodwn.
So why is it going again?
How you do use a state to update and preserve another state and how do you do a timed function where it'll start deducting points in my app?