Make Video Flash Cards in Next.js

Milecia

There will be short videos uploaded to Cloudinary on some educational topic and they'll be rendered in random order on flashcards in the app, along with explainer text

Initial setup

The first thing you need is a Cloudinary account. You can make one for free here. This is where you'll be able to upload your videos to study from. Go ahead and create a new folder in your Cloudinary console called study-videos. If you have any videos ready, upload them to this folder.

Set up the Next app

Now we can move on to setting up the Next app. Open a terminal and run the following command:

1$ yarn create next-app --typescript

This will bootstrap the app for us. We'll be using Tailwind CSS for our styles so let's install that and a couple of other packages with the following commands:

1$ yarn add tailwindcss postcss autoprefixer
2$ npx tailwindcss init -p

You'll need to update the tailwind.config.js with the following snippet so that the Tailwind styles are applied correctly:

1// tailwind.config.js
2...
3content: [
4 "./pages/**/*.{js,ts,jsx,tsx}",
5 "./components/**/*.{js,ts,jsx,tsx}",
6 ],
7...

The last bit of setup for Tailwind is adding the directives to your global styles. Go to the styles folder and delete everything out of globals.css and replace it with the following:

1/* globals.css */
2@tailwind base;
3@tailwind components;
4@tailwind utilities;

Set up the .env file

We'll be working with the Cloudinary API to get all of the videos out of the study-videos folder we made earlier. At the root of the project, make a new file called .env. You'll need the three following values in order to make requests to the Cloudinary API. You can find them in your Cloudinary console.

1CLOUDINARY_API_KEY=your_jumble_of_numbers
2CLOUDINARY_API_SECRET=your_mix_of_characters
3CLOUDINARY_CLOUD_NAME=your_cloud_name

That's all for the setup! Now we can start building the UI for our flash cards.

Make the flashcard page

Go to the pages folder and open the index.tsx file and delete everything inside of it. This is where we're going to render the flashcard. Start by adding the following code to set up the component and a few types.

1// index.tsx
2import { useState } from "react";
3import type { NextPage } from "next";
4import Head from "next/head";
5
6interface Videos {
7 videos: string[];
8}
9
10interface FlashcardData {
11 question: string;
12 answer: string;
13 videoUrl: string;
14}
15
16const Flashcard: NextPage = () => {
17 return (
18 <div className="container mx-auto">
19 <Head>
20 <title>Study for this test</title>
21 <meta
22 name="description"
23 content="It's time to get ready for some certification"
24 />
25 </Head>
26 </div>
27 );
28};
29
30export default Flashcard;

Right now we've just imported a few things and defined the <Head> for this page. Now let's get the data from Cloudinary so we can make the flashcards.

Fetching video data from Cloudinary

We'll use Cloudinary's search API to get all of the videos in the study-videos folder we made earlier. Since this is a Next app, we'll use the getStaticProps function because this data needs to be available before the user's request. Add this code just above the default export statement.

1// index.tsx
2
3...
4export async function getStaticProps() {
5 const params: any = {
6 expression: 'folder:"study-videos"',
7 };
8
9 const folderSearch = Object.keys(params)
10 .map((key: string) => `${key}=${encodeURIComponent(params[key])}`)
11 .join("&");
12
13 const results = await fetch(
14 `https://api.cloudinary.com/v1_1/${process.env.CLOUDINARY_CLOUD_NAME}/resources/search?${folderSearch}`,
15 {
16 headers: {
17 Authorization: `Basic ${Buffer.from(
18 process.env.CLOUDINARY_API_KEY +
19 ":" +
20 process.env.CLOUDINARY_API_SECRET
21 ).toString("base64")}`,
22 },
23 }
24 ).then((r) => r.json());
25
26 const { resources } = results;
27
28 const videos: Videos = resources.map(
29 (resource: { secure_url: string }) => resource.secure_url
30 );
31
32 return {
33 props: {
34 videos,
35 },
36 };
37}
38
39export default Flashcard

First, we define the search parameter we need to target the correct folder. Then we encode the search so that it's in a URL-friendly format. You could expand this to use multiple search parameters. After that, we make the GET request with the search parameters and our authentication information in the headers and wait for the JSON response.

Next, we parse out the video URLs we need for the flashcard data and pass it to the Flashcard component as a prop. So we'll need to do a quick update to the component declaration line.

1// index.tsx
2
3...
4const Flashcard: NextPage<Videos> = ({ videos }) =>
5...

Now we can associate the videos with questions and answers.

Making the flashcard data

This is where you might pull in data from a different API to get the flashcard questions and answers, but we'll use some filler text for this example. Add the following code inside of the Flashcard component, above the current return statement.

1// index.tsx
2
3...
4const [flashcardData, setFlashcardData] = useState<FlashcardData[]>([]);
5const [flipCard, setFlipCard] = useState<boolean>(false);
6const [index, setIndex] = useState<number>(0);
7
8useEffect(() => {
9 const questionsAnswers = [
10 {
11 question: "What was the name of the first computer invented?",
12 answer: "Electronic Numerical Integrator and Computer (ENIAC)",
13 },
14 {
15 question: "When was the first 1 GB disk drive released in the world?",
16 answer: "1980",
17 },
18 {
19 question: "What is the full form of UPS?",
20 answer: "Uninterruptible Power Supply",
21 },
22 {
23 question: "Difference between “ == “ and “ === “ operators.",
24 answer:
25 "“==” is used to compare values and “ === “ is used to compare both values and types",
26 },
27 {
28 question: "What are callbacks?",
29 answer:
30 "Functions that will be executed after another function gets executed",
31 },
32 ];
33 const flashcardData = videos.map((video, i) => ({
34 question: questionsAnswers[i].question,
35 answer: questionsAnswers[i].answer,
36 videoUrl: video,
37 }));
38
39 setFlashcardData(flashcardData);
40}, [videos]);
41
42if (flashcardData.length === 0) return <div>Loading...</div>;

This code sets some initial states for the flashcards and the way they will be rendered. Then we create the flashcard data and update it whenever there's a change to the videos. Next, we have a loading state that gets rendered while the flashcards are being created.

All that's left now is actually rendering the flashcards in a user-friendly way.

Rendering the flashcards

We want to cycle through the cards a number of times to learn the right answers to questions. So we'll have the cards on a continuous loop that users can keep clicking through, regardless of the number of questions. Add the following code below the <Head> element.

1// index.tsx
2
3...
4<main className="my-24 p-6 rounded-md border-4 border-indigo-200 border-l-indigo-500 h-fit">
5 {!flipCard ? (
6 <div className="text-center flex flex-col space-y-16">
7 <h1 className="font-bold text-3xl">Topic: Tech Stuff</h1>
8
9 <p className="">{flashcardData[index].question}</p>
10 <button
11 className="rounded-full bg-emerald-400 py-2 px-4 text-stone-100 w-1/2 mx-auto"
12 onClick={() => setFlipCard(true)}
13 >
14 Get answer and watch video
15 </button>
16 </div>
17 ) : (
18 <div className="text-center flex flex-col space-y-4">
19 <video
20 controls
21 src={flashcardData[index].videoUrl}
22 width="80%"
23 className="mx-auto"
24 ></video>
25 <p className="">{flashcardData[index].question}</p>
26 <p>{flashcardData[index].answer}</p>
27 <button
28 className="rounded-full bg-violet-400 py-2 px-4 text-stone-100 w-1/2 mx-auto"
29 onClick={() => {
30 const updatedIndex =
31 index + 1 > flashcardData.length - 1 ? 0 : index + 1;
32 setFlipCard(false);
33 setIndex(updatedIndex);
34 console.log(index);
35 }}
36 >
37 Next question
38 </button>
39 </div>
40 )}
41</main>
42...

This code represents the two sides of a flashcard. One side will just have the question and a button to go to the video and answer. If you run you app with yarn dev at this point, you should see something like this.

The other side will have the video and the answer to the question.

That's it! Now you have an app that will let you make flashcards quickly and it can be expanded to include a number of other features.

Finished code

You can find the complete code for this in the video-flashcards folder of this repo or you can check it out in this Code Sandbox.

Conclusion

Using videos as a part of any study routine can help make the information stick. With this app, you can include short videos from lectures or classes you're taking to get a quick review of everything that you need to know for a particular topic.

Milecia

Software Team Lead

Milecia is a senior software engineer, international tech speaker, and mad scientist that works with hardware and software. She will try to make anything with JavaScript first.