Build Quiz App with ReactJS
14 Dec 2015The best way to learn is practice. So I create my own quiz app based on this article.
Let’s start
We’ll split our application into 2 components, a main component Quiz
and stateless Question
component.
Quiz Component
First we create our initial state.
export default class Quiz extends React.Component {
constructor(props) {
super(props)
this.state = {
quiz: {},
index: 0,
numberOfQuestions: 0,
score: 0,
answers: [],
completed: false
}
}
}
For this example, we load quiz data from a json file and update the quiz state. We use the componentDidMount
method.
I use jquery getJSON
, but in real application you might want to use asynchronous fetch
or some thing like that.
componentDidMount() {
$.getJSON('./data/quiz.json', function(result) {
this.setState({quiz: result})
this.setState({'numberOfQuestions': result.questions.length})
}.bind(this))
}
Here what the data in json
file look like:
{
"title": "Elementary Math Quiz",
"questions": [
{
"question": "5 * 4 =",
"answers": [
{
"point": 1,
"label": "20"
},
{
"point": 0,
"label": "10"
},
{
"point": 0,
"label": "30"
},
{
"point": 0,
"label": "25"
},
]
}
...
]
}
render() {
const {
quiz, index, numberOfQuestions, score
} = this.state
return (
<div>
<h1>{quiz.title}</h1>
{this.state.completed ?
<div>
<p>Congratulation, you finish the quiz</p>
Your score is {score}
</div>
:
<div>
<h2>Question {index + 1} of {numberOfQuestions}</h2>
{quiz.questions && index < quiz.questions.length ?
<Question
question={quiz.questions[index]}
index={index}
onAnswerSelected={(event) => this.handleAnswerSelected(event)}
onSubmit={() => this.handleSubmit()}
/>
: ''}
</div>
}
</div>
)
}
We have 2 more methods on Quiz component, handleSubmit
will handle user clicking submit button and handleAnswerSelected
will handle user choosing answer on question.
On handleSubmit
we increment the index to show next question and at the end of quiz set completed
as true
and calculate the score.
handleSubmit() {
if (this.state.index + 1 < this.state.numberOfQuestions) {
this.setState({'index': this.state.index + 1})
} else {
this.setState({'completed': true})
let score = this.state.score || 0
this.state.answers.map((answer, i) => (
score = score + this.state.quiz.questions[i].answers[answer].point
))
this.setState({'score': score})
}
}
We also need to handle user clicking an answer, update the answers
state with this answer.
handleAnswerSelected(event) {
let list = [...this.state.answers.slice(0, this.state.index),
parseInt(event.target.value),
...this.state.answers.slice(this.state.index + 1)]
this.setState({'answers': list})
}
Question Component
The stateless question component will render question based on props from the main component Quiz.
question
object will contain question text and array of answersindex
represents the order of the questiononAnswerSelected
onSubmit
import React from 'react'
const Question = ({
question,
index,
onAnswerSelected,
onSubmit
}) => {
return (
<div>
<h3>{question.question}</h3>
<ol type="a">
{question.answers.map((answer, i) =>
<li key={`${index}-${i}`}>
<input type="radio" name={`question_${index}`} id={`question_${index}_answer_${i}`} defaultChecked={false} value={i} onChange={onAnswerSelected} />
{' '}
<label htmlFor={`question_${index}_answer_${i}`}>{answer.label}</label>
</li>
)}
</ol>
<button onClick={onSubmit}>Submit</button>
</div>
)
}
export default Question
Here what the application looks like :
Obviously this is not perfect, you can cheat and lookup the JSON for the answers. A better solution would be send all the answers and calculate the result on the server.
Github You can get full source code here. https://github.com/gchandrasa/quiz-react