I want to build a flashcard app.
I'm imagining a dataset of questions/answers and I can display each question as a "card". When I click on the card, it will "flip" and show me the answer.
I don't have as clear of a picture of how I will implement these details so when planning, I'll list these features as user stories:
- Users can keep score
- There is a form so that users can add new flashcards
- Users can make "playlists" or "quizzes" that show a specific set of flashcards
First, create the app using Vite: npm create vite@latest (you may be prompted to install the latest version of vite)
npm create vite@latest
# Name it flashcards
# Select React
# Select JavaScript
cd flashcards
npm i
# Delete the contents of App.jsxI know that I'll need some flashcard data to render.
To get myself started, I used ChatGPT to give me a dataset of flashcards rather than come up with my own set of questions. I asked for the data in JSON format so that I could easily import it into my app.
I then stored the resulting data in a .json file called src/db/flashcards.json (I made a src/db folder since this is sort of like my "database").
{
"flashcards": [
{
"id": 1,
"question": "What is React?",
"answer": "React is a JavaScript library for building user interfaces."
},
{
"id": 2,
"question": "What is JSX?",
"answer": "JSX is a syntax extension for JavaScript used with React to describe what the UI should look like."
},
{
"id": 3,
"question": "What are components in React?",
"answer": "Components are the building blocks of a React application. They encapsulate logic and UI."
},
...
]
}ChatGPT did a great job of giving me data in a format that I could easily use.
- The data was in an Array which means I can render a "card" for each object using
.map()in aul - Each flashcard object had:
- an
idwhich I can use for list itemkeyprops and much more - a
questionand ananswerwhich will be useful for when I want to toggle which text I show to the user.
- an
JSON Server is a tool to we use to spin up a mock API. It basically lets us turn any properly formatted .json file into a full API running on localhost.
It is a great alternative when you don't have the time to build out a full Express API. It does have its limitation in that it cannot support a robust relationships database. Read the JSON Server documentation for more information.
Using the JSON file we created above, we can create a mock API. To set it up we can:
- Run
npm install -g json-serverto install json server globally - Create the
.jsonfile. We did this already:db/flashcards.json - From the root of your vite project, split your terminal and run
json-server --watch db/flashcards.json --port 4000to start a mock back-end server on port 4000.
- Now, you will have an API that you can access via the URL http://localhost:4000/flashcards (try visiting that URL in your browser!)
json-server only works if the .json file is in the proper format. The JSON file needs to store a JSON object with a top-level property that names the resource to be fetched.
Something like (feel free to copy this):
{
"flashcards": [
{
"id": 1,
"question": "What is React?",
"answer": "React is a JavaScript library for building user interfaces."
},
{
"id": 2,
"question": "What is JSX?",
"answer": "JSX is a syntax extension for JavaScript used with React to describe what the UI should look like."
},
{
"id": 3,
"question": "What are components in React?",
"answer": "Components are the building blocks of a React application. They encapsulate logic and UI."
}
]
}In this example, "flashcards" is the top-level property which makes http://localhost:4000/flashcards a valid endpoint. When we send a GET request to that endpoint, we'll get back the value of "flashcards".
Q: What would be the endpoint(s) created if this were our JSON file?
{
"friends": [
"ben",
"gonzalo",
"carmen"
],
"message": {
"data": "hello world"
}
}Q: How would I make a http://localhost:4000/flashcards/react or http://localhost:4000/flashcards/fetch endpoint?
Answer
{
"flashcards": {
"react": [
{
"id": 1,
"question": "What is React?",
"answer": "React is a JavaScript library for building user interfaces."
},
...
],
"fetch": [
{
"id": 1,
"question": "What does fetch do?",
"answer": "fetch sends an HTTP request to the provided url."
},
...
]
}
}To make the MVP, the app can be quite simple. Just render a ul with an li "card" for each flashcard object. So I basically just need my App component and Flashcard component. I'll then map each object in the dataset to a <Flashcard />.
For the MVP, here is what I came up with:
The Flashcard component should be focused solely on rendering a single flashcard object. It can maintain its own state to toggle back and forth between showing the question and the answer.
import { useState } from 'react'
const Flashcard = ({ flashcard }) => {
const [text, setText] = useState(flashcard.question)
const [backgroundColor, setBackgroundColor] = useState('lightblue')
const flipCard = () => {
if (text === flashcard.question) { // show the answer
setText(flashcard.answer);
setBackgroundColor('lightgreen');
} else {
setText(flashcard.question); // show the question
setBackgroundColor('lightblue');
}
}
// set the style dynamically using the backgroundColor state
const style = { background: backgroundColor }
return (
<li className="card" onClick={flipCard} style={style}>
<p>{text}</p>
</li>
)
}- The
Flashcardcomponent takes in aflashcardobject as a prop. - It also keeps track of two state values:
textandbackgroundColorwhich can be toggled between showing the question and showing the answer - We provide a
styleprop to dynamically set the style of the component using thebackgroundColorstate - We render the flashcard as an
liwith anonClickprop, astyleprop, and with thetextstate rendered.
The App component needs to fetch the set of flashcards from the json-server URL http://localhost:4000/flashcards when the component first loads and then use that data to render a list of flashcards.
import './App.css'
import { useState, useEffect } from 'react'
import fetchData from './utils/fetchData'
// Check out the helper function ^
const Flashcard = () => {
// flashcard component
}
function App() {
const [flashcards, setFlashcards] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
const doFetch = async () => {
const [data, error] = await fetchData('http://localhost:4000/flashcards');
if (data) setFlashcards(data);
if (error) setError(error);
};
doFetch();
}, []); // run the effect only once
// Conditionally render the error message
if (error) return <p>{error.message}. Refresh to try again.</p>
return (
<>
<h1>Flash Cards</h1>
<ul>
{
flashcards.map((flashcard) => <Flashcard key={flashcard.id} flashcard={flashcard} />)
}
</ul>
</>
)
}
export default AppLet's break it down:
- The
Appkeeps track offlashcardsanderrorstate. - We use
useEffectto fetch the flashcard data from our json-server when the component first renders (and only that one time).- Then we either invoke
setFlashcardsorsetErrordepending on the returned data.
- Then we either invoke
- The
Appcomponent maps over theflashcardsdata, creating aFlashcardcomponent for eachflashcardobject. - When rendering a list of components, we use the
flashcardobject'sidas thekeyand pass along theflashcardobject as a prop. - Since I kept the starting CSS styles that came with the Vite project, it actually looks okay.
- Organize my components into separate files
- Build out stretch features
- Add better styling

