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

A post from /r/shittymoviedetails

This one is from /r/moviedetails

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:

  1. A function to query the Reddit API to get the post
  2. The quiz card that shows the post generated
  3. A popup to show whether the user chose correctly or not
  4. 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.

  1. React

    • Popular library, been using it at work and am fairly comfortable with it
  2. Chakra UI

    • 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.
  3. Vite

    • Usually when I initialise a React application, my first instinct is to reach for either Create-React-App if I want something simple, or NextJS 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 Screenshot 2022-06-04 at 2.03.48 PM.png

    And with that all out of the way, lets look at the code..


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

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:

  1. We store the top 50 posts from both subreddits into a list
  2. We select a post and pass it through the checkQuestion function.
    1. If it fails, we pick another post and check again
    2. 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

  1. Image props.url
  2. Title text props.title
  3. Subreddit props.subreddit (so we can check whether they picked the right option)
  4. Link props.permalink (so they can view the full post themselves)

Screenshot 2022-06-04 at 2.43.12 PM.png

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.

Screenshot 2022-06-04 at 2.50.40 PM.png

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!