I working on a MERN project for learning purposes (not YT follow-up). I have a user model with a rank of 0 as default and with different score fields. I have created an endpoint that when the score fields meet certain conditions, the user rank gets updated 1. So far so good here and it works. My next step is to show a ‘you ranked up’ pop-up when the user rank changes and I am not sure what the best practice is for that. It’s a math-practice challenge and I want the pop-up to show between the end of the game and final score component. Any help is appreciated.
Endpoint:
import User from "../models/userModel.js";
import jwt from "jsonwebtoken";
import asyncHandler from "express-async-handler";
export const rankUp = asyncHandler(async (req, res) => {
// Extract token from headers
const token = req.headers.authorization.split(" ")[1];
// Find the user by token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Find user by id
const user = await User.findOne({ _id: decoded.id });
if (!user) {
return res.status(404).send("User not found");
}
let newRank;
if (
user.userStats.totalScore === 5000 &&
user.userStats.totalAdditionScore >= 1000 &&
user.userStats.totalAdditionScore >= 1000 &&
user.userStats.totalSubtractionScore >= 1000 &&
user.userStats.totalMultiplicationScore >= 1000 &&
user.userStats.totalOrderedScore >= 1000
) {
newRank = 1;
} else {
throw new Error('Conditions not met')
}
// Save the updated user
const updatedUserRank = await User.findByIdAndUpdate(
{ _id: decoded.id },
{ $set: { userRank: newRank } },
{ new: true }
);
if (updatedUserRank) {
res.json({updatedUserRank});
} else {
res.status(400);
throw new Error("Something went wrong");
}
});
The component that I want to show the pop-up:
import { Divider, List, TextField, Typography } from "@mui/material";
import React, { useEffect } from "react";
import Navbar from "../components/Navbar";
import Container from "@mui/material/Container";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import { useState } from "react";
import { useNavigate } from "react-router";
import GameInfo from "./GameInfo";
import { useSelector, useDispatch } from "react-redux";
import {
earnLife,
gainPoints,
gainTime,
isFinished,
loseLife,
loseTime,
restart,
} from "../features/gameSlice";
import { updateScore } from "../statsHandler";
const correctAnswer = <Typography>Correct!</Typography>;
const wrongAnswer = <Typography>Wrong!</Typography>;
const enterAnswer = <Typography>Enter your answer!</Typography>;
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 [streak, setStreak] = useState(0);
const seconds = useSelector((state) => state.game.seconds);
const points = useSelector((state) => state.game.points);
const lives = useSelector((state) => state.game.lives);
const gameOver = useSelector((state) => state.game.isFinished);
const gameStart = useSelector((state) => state.game.startGame);
const user = JSON.parse(localStorage.getItem("user"));
const token = user.token;
let finalScore = points;
const navigate = useNavigate();
// FIX THE UNDEFINED ISSUE
// FIX THE UNDEFINED ISSUE
// FIX THE UNDEFINED ISSUE
useEffect(() => {
if (gameOver) {
updateScore(finalScore, operation, token);
}
}, [gameOver]);
useEffect(() => {
if (correctValue && streak === 4 && lives < 4) {
dispatch(earnLife());
setStreak(0);
}
}, [streak]);
useEffect(() => {
setCalculatedNums(calculation());
setGenerateNewNumbers(false);
setCorrectValue(false);
setEnteredValue("");
}, [generateNewNumbers]);
const dispatch = useDispatch();
const timerValid = lives > 0 && seconds > 0 && gameStart;
const newChallenge = () => {
setIsIncorrect(false);
setHaveToEnterAnswer(false);
dispatch(restart());
};
const handleCount = () => {
if (timerValid) {
dispatch(loseTime());
}
};
useEffect(() => {
if (lives === 0 || seconds === 0) {
dispatch(isFinished());
}
}, [lives, seconds]);
useEffect(() => {
let interval;
if (timerValid) {
interval = setInterval(() => {
handleCount();
if (lives === 0 || seconds === 0) {
clearInterval(interval);
}
}, 1000);
}
return () => {
clearInterval(interval);
};
}, [timerValid]);
const submitHandler = () => {
if (correctValue) {
setGenerateNewNumbers(true);
dispatch(gainPoints());
dispatch(gainTime());
setStreak(streak + 1);
console.log(user);
}
if (+enteredValue === calculatedNums.result) {
setCorrectValue(true);
} else if (enteredValue.length === 0) {
setHaveToEnterAnswer(true);
} else {
setIsIncorrect(true);
dispatch(loseLife());
setStreak(0);
}
};
const inputValueHandler = (value) => {
setIsIncorrect(false);
setHaveToEnterAnswer(false);
setEnteredValue(value);
};
const submitOrTryNewOne = () => {
return correctValue ? "Try new one" : "Submit";
};
const goHome = () => {
navigate("/");
dispatch(restart());
};
return (
<>
<Navbar />
{seconds && lives > 0 ? (
<>
<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>
</>
) : (
<>
<List
sx={{
marginTop: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography fontSize={28}>GAME OVER</Typography>
<Divider></Divider>
<Typography sx={{ marginTop: 2 }} fontSize={28}>
Final Score: {points}
</Typography>
<Divider></Divider>
<Button
sx={{ marginTop: 2 }}
variant="contained"
size="large"
onClick={newChallenge}
>
New Challenge
</Button>
<Button
sx={{ marginTop: 2 }}
variant="contained"
size="large"
onClick={goHome}
>
Home Page
</Button>
</List>
</>
)}
</>
);
};
export default MainInput;