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 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:
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
Create-React-App
if I want something simple, or NextJS
if I’m thinking of something more fully featured such as server side
rendering.Webpack
found in CRA that’s supposed to be much faster.npm init vite@latest my-react-app --template react-ts
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:
checkQuestion
function.
setter
function.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
props.url
props.title
props.subreddit
(so we can check whether they picked the right
option)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)
}
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!
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!
Thanks for reading till the end!