'setTimeout in jsx component
I have a question about how to show stuff after a few seconds using setTimeout in a jsx component file.
I have been building a web quiz app and I want to show answers after a few seconds so that users can see a question first then be able to answer. I write setTimeout when I map through answers of a quiz. It outputs console.log a result, but never show answers but outputs some weird number on the window.
import { useEffect, useState } from 'react';
import { collection, onSnapshot, query, orderBy, where, limit, getDocs } from 'firebase/firestore';
import { useLocation } from 'react-router-dom';
import Loading from 'react-simple-loading';
import {db} from '../config/firebase';
import GoodBad from './GoodBad';
import GoNextQuizBtn from './GoNextQuizBtn';
import GoPrevQuizBtn from './GoPrevQuizBtn';
import QuizResultWindow from './QuizResultWindow';
import { biCircle, biPlus } from '../icons/icons';
const Test = ({currentUser}) => {
const [quizzes, setQuizzes] = useState([]);
// const [clickedAnswers, setClickedAnswers] = useState([]);
const [currentQIndex, setCurrentQIndex] = useState(0);
const [points, setPoints] = useState(0);
const [usersCorrectAnswers, setUsersCorrectAnswers] = useState([]);
const [clickedAnswerIndex, setClickedAnswerIndex] = useState();
const location = useLocation();
const selectedCategories = location.state.selectedCategories;
const [time, setTime] = useState(10);
console.log(`selectedCategories => `, selectedCategories, "desu")
// console.log(currentUser)
useEffect(() => {
// todo: Get new quizzes
const getQuizzesFromPassedCategories = async () => {
const collectionRef = collection(db, 'quizzes');
let tempQuizzes = [];
if (selectedCategories.includes("all")) {
const querySnapshot = await getDocs(collectionRef);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
tempQuizzes.push({ ...doc.data(), id: doc.id });
});
} else {
for (let i = 0; i < selectedCategories.length; i++) {
const c = selectedCategories[i];
const q = query(collectionRef, where("category", "==", c))
console.log(c, "=>", q)
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data());
// dont forget to add id, refer onSnapshot in QuizHome
tempQuizzes.push({ ...doc.data(), id: doc.id });
});
}
}
setQuizzes(tempQuizzes);
console.log(quizzes)
};
getQuizzesFromPassedCategories();
}, []);
// console.log(quizzes)
const handleJudge = async (e, answer, quiz, answerIndex, quizIndex) => {
// It may be unnecessary to add 1. I jsut thought users don't like index 0 for answer/quiz 1.
answerIndex++;
quizIndex++;
const correctAnswerIndex = quiz.correctAnswer;
console.log(
`answer => ${answer}, answerIndex => ${answerIndex}, correctAnswerIndex => ${correctAnswerIndex}, quizIndex => ${quizIndex}`
);
setClickedAnswerIndex(answerIndex);
// add some styles to answers depending on correct or not
if (correctAnswerIndex === answerIndex) {
setPoints(prevState => prevState + 1);
setUsersCorrectAnswers([...usersCorrectAnswers, quizIndex]);
e.target.className = await 'selected correctAnswerClicked';
} else {
e.target.className = await 'selected incorrectAnswerClicked';
}
};
const goNextQuiz = () => {
if (currentQIndex !== quizzes.length) {
setCurrentQIndex(prevState => prevState + 1);
}
setClickedAnswerIndex();
};
const goPrevQuiz = () => {
if (currentQIndex !== 0) {
setCurrentQIndex(prevState => prevState - 1);
} else {
setCurrentQIndex(currentQIndex);
}
setClickedAnswerIndex();
};
return (
<div className='quizContainer'>
{quizzes.length === 0 && (
<div className="loading">
<Loading color={'#005bbb'} />
</div>
)}
{quizzes.map((quiz, quizIndex) => {
if (quizIndex === currentQIndex) {
return (
<div key={quiz.id} className='quiz'>
<div className='quizHeader'>
<span className='createdBy'>Created by: {quiz.user.username ? quiz.user.username : "Anonymous"}</span>
<span className='quizNumber'>
{quizIndex + 1}/{quizzes.length}
</span>
</div>
<div className='quizQuestionContainer'>
<p className='quizQuestionText'>{quiz.question}</p>
</div>
<ul
className={
clickedAnswerIndex
? 'quizAnswersContainer answerDefined'
: 'quizAnswersContainer'
}
>
{setTimeout(() => {
console.log("set time out")
quiz.answers.map((answer, answerIndex) => (
<li
key={answerIndex}
onClick={e => {
handleJudge(e, answer, quiz, answerIndex, quizIndex);
}}
className={
clickedAnswerIndex &&
answerIndex + 1 === clickedAnswerIndex
? 'selected'
: null
}
>
<span className='answer'>{answer}</span>
<div className='correctIncorrectIcons'>
<span className='correctIcon'>{biCircle}</span>
<span className='incorrectIcon'>{biPlus}</span>
</div>
</li>
))
}, 1000)}
</ul>
<div className='quizFooter'>
{quizIndex !== 0 ? (
<GoPrevQuizBtn
goPrevQuiz={goPrevQuiz}
text='Prev'
disable=''
/>
) : (
<GoPrevQuizBtn
goPrevQuiz={goPrevQuiz}
text='Prev'
disable='disable'
/>
)}
<GoodBad quiz={quiz} currentUser={currentUser} />
{quizIndex + 1 === quizzes.length ? (
<GoNextQuizBtn goNextQuiz={goNextQuiz} text='Result' clickedAnswerIndex={clickedAnswerIndex ? true : false } />
) : (
<GoNextQuizBtn goNextQuiz={goNextQuiz} text='Next' clickedAnswerIndex={clickedAnswerIndex ? true : false } />
)
}
</div>
</div>
);
}
})}
{quizzes.length !== 0 && currentQIndex >= quizzes.length ? (
<QuizResultWindow
usersCorrectAnswers={usersCorrectAnswers}
points={points}
quizzes={quizzes}
/>
) : (
null
)}
</div>
);
};
export default Test;
Solution 1:[1]
setTimeoutreturns an ID, so that's what I'd expect to see in your render.- Running
quiz.answers.map()a second later doesn't change the already-rendered output. - Even if 2 were not true, the function you're passing to setTimeout doesn't return anything.
What I'd suggest is that you use state to determine whether to show the answers, and use an effect to set that state to true a second after rendering.
Would look something along the lines of:
function MyComponent () {
const [showAnswers, setShowAnswers] = useState(false);
useEffect(() => {
const timeoutId = setTimeout(setShowAnswers, 1000, true);
return () => clearTimeout(timeoutId);
}, [])
return (
<div>
{ showAnswers && (
<ul>
{ quiz.answers.map((answer, index) => <li key={index}>...</li>) }
</ul>
)}
</div>
)
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
