There are few things as exciting as the “I know that one!” feeling one gets in a trivia game. The rush of being able to answer a niche and random question is a major reason why game shows have been a TV staple for almost as long as televisions have existed.
I recently came across OpenTDB, a free user-contributed trivia question database. Naturally, my first thought was to use it to create a trivia app. Though not incredibly complex, this project serves as a fun introduction to a number of React topics:
Developing and Deploying a React App with Codesphere
Working with an API
Using useEffect and useState
The final result:
Creating a React App with Codesphere
Codesphere is an online, collaborative programming environment and cloud provider. With Codesphere you can build your web app ready to scale without all the cumbersome config. In this project, Codesphere is going to allow us to create and deploy our React app seamlessly.
Codesphere is currently in early access but will launch fully soon. If you would like to join the early access, I have a number of keys that I can give out — leave me a comment with your email and I’ll send you one.
Once you are signed in, Codesphere should loan you in with an example project. We are going to delete the contents of the example project by running the following command in the terminal
rm -r ../app/*
We can then go ahead and make our React App from the terminal with the command:
npx create-react-app trivia
Finally, to make sure everything is running smoothly, we can run:
cd trivia && npm start
Before we continue, it will be helpful to get rid of the create-react-app example and have our App.js look like the following:
Accessing the OpenTDB API
Open Trivia Database(OpenTDB) offers us an API that we can call to receive a given number of questions on a given topic. You can generate a custom API Url here:
Open Trivia DB
*To get started using the Open Trivia DB API, use this URL: For more settings or help using the API, read along below…*opentdb.com
In this project, we are going to be using the following API URL:
const url = "[https://opentdb.com/api.php?amount=10&category=19&type=multiple](https://opentdb.com/api.php?amount=10&category=18&type=multiple)"
This URL will give us ten multiple-choice questions in the Math category, but I encourage you to pick whatever topic interests you!
Before we start, make sure to import useEffect and useState at the top of our App.js:
*import {useState, useEffect} from 'react';*
Our next step is going to be defining a number of stateful variables with useState:
const [questions, setQuestions] = useState([])
const [loaded, setLoaded] = useState(false)
const [qInd, setQInd] = useState(0)
The questions variable is going to be an array of question objects we get from the API. The loaded variable is going to let us know if the questions have been loaded yet, so we don’t accidentally try to access them too early. Finally, qInd(As in “question index”) is going to track the current question we are on in our array.
const loadQuestions = async () => {
let response = fetch(url).then(response => response.json()).then(data => {
setQuestions(data.results)
setLoaded(true)
})
}
To make sure that it’s working properly, let’s display the current question in our app. We have to make sure, however, that the question loads only if our loaded variable is true. The app will crash otherwise because you are attempting to load an element from an empty questions array.
return (
<div className="App">
{loaded && <div>
<p className = "Question">{questions[qInd].question}</p>
</div>
}
</div>
);
This should render the following:
Accepting User Inputs
If you look at the question object’s structure, the correct_answer value is stored separately from the rest of our answers. To get all our answers in the same place, we are going to insert the correct_answer randomly among our incorrect_answers whenever we load a question.
We’ll adjust our loadQuestions function like so:
const loadQuestions = async () => {
let response = fetch(url).then(response => response.json()).then(data => {
insertCorr(data.results[0].incorrect_answers, data.results[0].correct_answer)
setQuestions(data.results)
setLoaded(true)
})
}
Where insertCorr() is defined as follows:
function insertCorr(arr, corr) {
const randInd = Math.floor(Math.random() * 4)
arr.splice(randInd, 0, corr)
}
Now that all of our questions are in a singular array, we can map through them and create buttons fairly easily:
return (
<div className="App">
{loaded && <div>
<p className = "Question">{questions[qInd].question}</p>
* {questions[qInd].incorrect_answers.map((a) => {
return <button key = {a} onClick = {(e) => handlePrsd(e, a)}>{a}</button>
})}
*</div>
}
</div>
);
For simplicity, we are going to set each buttons’ key to be its answer and have it call a handlePrsd function which we will define next. We are going to have this function accept two parameters: The click event and the answer that was pressed.
For our handlePrsd function, we want it to iterate to the next question. If we are at the end of our question array, we want it to load new questions:
*const handlePrsd = (e, ans) => {
e.preventDefault()
if(qInd + 1 < questions.length) {
insertCorr(questions[qInd + 1].incorrect_answers, questions[qInd + 1].correct_answer)
setQInd(qInd + 1)
} else {
loadQuestions()
setQInd(0)
}
}*
Now we should see the following:
Managing Score
The next step is going to be to manage the user’s score. To do so, we are going to create some stateful variables:
const [score, setScore] = useState(0)*
*const [lastAns, setLastAns] = useState('black')
The score variable will of course store the user’s score. The lastAns variable is going to store the color we want to display the score in, which will be black by default, green if the last answer was correct, and red if the last answer was incorrect.
We will then add a p tag in our App to display the score value in the correct color:
<p className = "Score" style=
{{color: lastAns}}
Score: {score}
Finally, we need to update our handlePrsd function to change the score if correct. To do so, we will check if the pressed answer is the same as the correct answer. If so, we will add ten points, if not, we will deduct 10.
*const handlePrsd = (e, ans) => {
e.preventDefault()
if(ans == questions[qInd].correct_answer) {
setScore(score + 10)
setLastAns('green')
} else {
setScore(score - 10)
setLastAns('red')
}*
* if(qInd + 1 < questions.length) {
insertCorr(questions[qInd + 1].incorrect_answers, questions[qInd + 1].correct_answer)
setQInd(qInd + 1)
} else {
loadQuestions()
setQInd(0)
}
}*
And our final result is:
There we go! Functional, but quite ugly. Let’s style it!
Making Everything Look Pretty
.App {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
padding-top: 100px;
text-align: center;
}
button {
color: #fff;
text-decoration: none;
background: #1b95db;
padding: 20px;
border-radius: 10px;
font-size: 22px;
display: inline-block;
border: none;
transition: all 0.4s ease 0s;
margin: 20px;
border: 2px solid black;
}
button:hover {
background: #3db1f5;
}
.Question {
font-size: 32px;
margin-right: 50px;
margin-left: 50px;
}
.Score {
font-size: 20px;
}
Deploying with Codesphere
Deploying in Codesphere is as easy as running:
npm start
in your terminal.
We can access the deployed web application like so:
Next Steps
This is obviously the bare minimum for a trivia game, but a lot of steps can be taken to make this better:
Buff the CSS and make it look great!
Allow users to change the category, difficulty, or type of questions. Edit the API URL based on their preferences!
Allow multiple players to play at once!
Top comments (1)
Awesome!