I built a quiz app using the Reddit API
A quiz app built using the Reddit API and Vite.
Inspiration
I’ve been a long time user of Reddit, and one of the subreddits I’m subscribed to is called /r/shittymoviedetails. The subreddit is pretty self explanatory, it mocks the actual subreddit /r/moviedetails, where people post interesting things they’ve noticed about movies.
A post from /r/shittymoviedetails
This one is from /r/moviedetails
The vast majority of posts on /r/shittymoviedetails are really quite shitty, and it’s pretty obvious that it’s a joke. But some are really good and sometimes I find myself doing a double take and being shocked when I find out which subreddit a post actually came from.
So I thought it’d be fun to build a quiz app where users guess whether a post is legit or shit.
Before we begin, you can try out the app here, or view the full source code here.
The Plan
The journey of a thousand miles begins with a simple step ~ some old Chinese guy
This app is a fairly simple and straightforward affair. It requires just a few components:
- A function to query the Reddit API to get the post
- The quiz card that shows the post generated
- A popup to show whether the user chose correctly or not
- A simple tutorial that explains the context and gives the page some time to generate the data
Technologies
As with any project, you need to ask yourself what tools you want to use to build it. I settled on some basics (React), but decided to learn some new technologies.
-
React
- Popular library, been using it at work and am fairly comfortable with it
-
- I wanted to focus less on styling and opted to go with Chakra UI
- I have been using Material UI at work and really enjoyed working with UI libraries and wanted to see how Chakra UI compared to MUI.
- Verdict: quite clean and easy to setup, not as fully featured as MUI but the components are nice and reusable. One big plus point of Chakra is the theming and customization capabilities, but I didn’t delve too much into it so I won’t comment on it. Overall it was easy to use and had many core components out of the box that didn’t require too much time to incorporate.
-
- Usually when I initialise a React application, my first instinct is to
reach for either
Create-React-App
if I want something simple, orNextJS
if I’m thinking of something more fully featured such as server side rendering. - This time, I wanted to try something new, and I’d been hearing about Vite,
an alternative to
Webpack
found in CRA that’s supposed to be much faster. - Verdict:
- It was a pleasant developer experience, and Vite supports Hot Module Replacement out of the box, meaning you can add or remove modules while an application is running, without a full reload, allowing things like preserving application state and a really seamless and a really fast feedback loop during development.
- Truly lightning fast dev server starts & it took less than a minute to
initialise a new project using
npm init vite@latest my-react-app --template react-ts
And with that all out of the way, lets look at the code..
- Usually when I initialise a React application, my first instinct is to
reach for either
Calling the Reddit API
I know when you first start a new project, the documentation is the first place to look. So over to the Reddit API docs I went.
The Reddit API documentation was, in my opinion, absolute garbage.
They didn’t help. At all. Instead, the best source of information I got came from the redditdev community. Once I got the basics down, querying the Reddit API was actually decently straightforward, as due to the scope of the project, setting up OAuth was not necessary as I’d be making simple GET requests.
For example, a GET request will look like this:
https://www.reddit.com/r/shittymoviedetails/top.json?limit=50&t=all
- shittymoviedetails (the subreddit i’m querying)
- top - filter by top (you can do hot, random, trending, new)
- limit = 50 (gives you 50 posts)
- t = all (top of all time, you can do month/ week/ year)
Sidetrack: my initial plan was to call a random post each time, but I decided to go with the top 50 of all time and randomly pick one post from there, as “random” didn’t seem that random to me, and the quality of the posts were often bad.
export async function getBoth(setter: Function) {
const fetchDetail = fetch(TOP_DETAIL).then((res) => res.json())
const fetchShitty = fetch(TOP_SHITTY).then((res) => res.json())
const allData = Promise.all([fetchDetail, fetchShitty])
allData.then((res) => {
setter(res)
})
}
// by passing in a setter function, I can set the state when the api call ends
//Example
const App = () => {
const [questionList, setQuestionList] = useState({})
//useEffect to call this function only once, when the component first renders
useEffect(() => {
getBoth(setQuestionList)
}, [])
}
Because we want to make 2 API calls (one to get the shittymoviedetails and the
other for the actual moviedetails), and these calls don’t depend on each other,
we can make the calls in parallel, using Promise.all
. Read more about it
here. This is something super useful that
I’d definitely be using more in the future.
The question list now consists of 100 posts, half shitty, half legit. So we need a function to randomly pick one and pass it into our quiz app.
export function getQuestion(
questionList: any,
setter: Function,
loader: Function
): void {
let choice = randomChoice([0, 1]) //detail [0] or shitty [1]
let chosenQns = randomChoice(questionList[choice].data.children).data
while (checkQuestion(chosenQns) === false) {
chosenQns = randomChoice(questionList[choice].data.children).data
}
setter(chosenQns)
loader(false)
}
But before we pass that post in, we need some checks. Reddit posts supports selftext, videos, or gallery posts, which we don’t want showing up. The only type of post we want showing up are Images. So we need a function that checks this for us.
const checkQuestion = (question: any) => {
if (
question.is_gallery ||
question.is_self ||
question.is_video ||
Object.keys(question.media_embed).length !== 0
)
return false
else return true
}
And with that, we’ve got our function that generates a post for us.
To recap:
- We store the top 50 posts from both subreddits into a list
- We select a post and pass it through the
checkQuestion
function.- If it fails, we pick another post and check again
- Else, we return the post and pass it back into the component using the
setter
function.
The QuizCard component
First, lets take a look at the data we’re passing into the QuizCard.
<QuizCard {...question} nextQuestion={nextQuestion} />
By using {…question}
, we’re passing all the key-value pairs of question into
the QuizCard.
If we console.log() it out, we see a massive object. You can see an example of how it looks here.
But most of this information isn’t relevant to us. We’re only looking for a couple of things
- Image
props.url
- Title text
props.title
- Subreddit
props.subreddit
(so we can check whether they picked the right option) - Link
props.permalink
(so they can view the full post themselves)
Now, we need to figure out a way to determine which button they clicked, and whether it was the right option.
To figure out which button they clicked, we can create a handleSubmit
helper
function, that takes in 2 props, the event, and the option.
// We create a piece of state called correct and set it to be a Boolean
const [correct, setCorrect] = useState(Boolean);
const handleSubmit = (e: any, choice: string) => {
e.preventDefault();
//Using a ternary, we check for the subreddit and set the correct answer
const answer = props.subreddit === "MovieDetails" ? "detail" : "shitty";
//check whether the choice === answer, and set the correct state accordingly
setCorrect(answer === choice ? true : false);
onOpen();
};
// The buttons which onSubmit, pass in the option the user has chosen
<Button mx={2} onClick={(e) => handleSubmit(e, "shitty")}>
💩 Shit
</Button>
<Button mx={2} onClick={(e) => handleSubmit(e, "detail")}>
✅ Legit
</Button>
We also pass a nextQuestion
function as a callback, which generates the new
question.
const nextQuestion = () => {
getQuestion(questionList, setQuestion, setLoading)
}
The Popup
So we’ve created the quiz card, passed in the necessary props, and we have a way to check the answer. Now we need to show the result to the user.
Since we’re using Chakra UI, we can use a Modal component to achieve this.
<Popup
isOpen={isOpen}
onClose={onClose}
result={correct}
nextQuestion={props.nextQuestion}
link={props.permalink}
/>
// We pass in the necessary props into the Popup component
Inside the Popup
component, we dynamically render the result using some
ternary operators.
export default function Popup({
isOpen,
onClose,
result,
link,
nextQuestion,
}: any) {
return (
<>
<Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay />
<ModalContent>
<ModalHeader>
{result ? "✅ You're right!" : "❌ You're wrong!"}
</ModalHeader>
<ModalCloseButton />
<ModalBody>
{result ? (
<>
You're right!
<br />
Or check out the full post{" "}
<Link
target="_blank"
href={`http://reddit.com${link}`}
color="teal.500"
>
here
</Link>
</>
) : (
<>
you're wrong!
<br />
Or check out the full post{" "}
<Link target="_blank" href={`http://reddit.com${link}`}>
here
</Link>
</>
)}
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" mr={3} onClick={onClose}>
Close
</Button>
<Button
variant="ghost"
onClick={() => {
onClose()
nextQuestion()
}}
>
Next
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
)
}
And we pass in the nextQuestion()
function through (we originally passed it in
to the QuizCard
component.
And we’re basically done!
Conclusion
This was a brief overview of the steps I took to create this simple app. Of course, this is not the full source code and I didn’t want to waste your time going through every single nitty gritty detail.
Some improvements can be made, such as a counter to keep track of the score, or currently users can be shown the same posts twice as the app doesn’t keep track of what posts have been seen, but I’ll leave that for another day.
You can try out the app here, or view the full source code here.
Thanks for reading!