Reputation: 3
I'm trying to implement a multiple choice quiz in Reactjs and TypeScript with an Express backend.
A Lesson component fetches a Lesson object from the API with a field for every multiple choice question, sets the current state to the lesson object, and then renders a MultipleChoiceQuestion component for each of them. I pass down a callback function to each of them for updating the score if the answer was correct or not. However, when I call that function I get this error:
Warning: Cannot update a component (`Lesson`) while rendering a different component (`MultipleChoiceQuestion`). To locate the bad setState() call inside `MultipleChoiceQuestion`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
MultipleChoiceQuestion@http://localhost:3000/static/js/main.chunk.js:1715:89
div
div
div
Lesson@http://localhost:3000/static/js/main.chunk.js:1473:108
Route@http://localhost:3000/static/js/vendors~main.chunk.js:34738:29
Switch@http://localhost:3000/static/js/vendors~main.chunk.js:34940:29
Router@http://localhost:3000/static/js/vendors~main.chunk.js:34373:30
BrowserRouter@http://localhost:3000/static/js/vendors~main.chunk.js:33993:35
App
Context@http://localhost:3000/static/js/main.chunk.js:2721:81 index.js:1
Here's my code with non-relevant pieces stripped away
import MultipleChoiceQuestion from './MultipleChoiceQuestion'
function Lesson() {
const [lesson, setLesson] = useState<Lesson>()
const [score, setScore] = useState(0)
const [maxScore, setMaxScore] = useState<number>(0)
const [attempt, setAttempt] = useState(false)
useEffect(() => {
// fetch from api
}, [lessonID])
const getQuestionScore = (questionScore: number, questionMax: number) => {
setScore(score + questionScore)
setMaxScore(maxScore + questionMax)
}
return (
<div>
{lesson.questions.map((q: MultipleChoiceQuestion) => {
const incorrect = q.incorrect
const correct = q.correct
return <MultipleChoiceQuestion
attempt={attempt} key={q.question}
question={q.question} scoreCallback={getQuestionScore}
correct={correct} incorrect={incorrect} />
})}
<button onClick={() => setAttempt(true)} >Test</button>
<h3>{score}/{maxScore}</h3>
</div>
</div>
</div>
)}
function MultipleChoiceQuestion(props: {
correct: string[],
attempt: any, incorrect: string[],
question: string, scoreCallback: any
}) {
const [hasReturnedScore, setHasReturnedScore] = useState(false)
const handleChange = (e: any) => {
setSelected(e.target.value)
}
if (props.attempt && !hasReturnedScore) {
setHasReturnedScore(true)
const score = calculateScore()
score ? props.scoreCallback(1, 1) : props.scoreCallback(0, 1)
}
return (
<div className="container">
<h2 className="title is-6">{props.question}</h2>
{props.correct.map((choice: any) => {
return <AnswerBox key={choice} choice={choice} selected={selected} changeCallback={handleChange} />
})}
{props.incorrect.map((choice: any) => {
return <AnswerBox key={choice} choice={choice} selected={selected} changeCallback={handleChange} />
})}
)
}
const AnswerBox = (prop: any) => {
return (
<label htmlFor="question" className="column radio" >
<input
id={prop.choice}
type="radio"
value={prop.choice}
checked={prop.selected === prop.choice}
onChange={prop.changeCallback}
name="choice" />
{prop.choice}
</label>
)}
I mistakenly thought it was a clever way to pass data from a child component to the parent but now I have no clue how to solve this problem.
Upvotes: 0
Views: 783
Reputation: 1550
Your this part
if (props.attempt && !hasReturnedScore) {
setHasReturnedScore(true)
const score = calculateScore()
score ? props.scoreCallback(1, 1) : props.scoreCallback(0, 1)
}
currently, it is written on render of child component (MultipleChoiceQuestion). It is wrong since it is calling a function while rendering only. You need to shift this inside some event handler (a function that gets called on some event like radio button clicked) or you shift this function inside useEffect
hook.
Upvotes: 1