Constructing a Tennis Trivia App With Subsequent.js and Netlify
At the moment we can be studying methods to construct a tennis trivia app utilizing Subsequent.js and Netlify. This expertise stack has develop into my go-to on many initiatives. It permits for fast development and straightforward deployment.
With out additional ado let’s leap in!
What we’re utilizing
- Subsequent.js
- Netlify
- TypeScript
- Tailwind CSS
Why Subsequent.js and Netlify
It’s possible you’ll assume that it is a easy app that may not require a React framework. The reality is that Subsequent.js offers me a ton of options out of the field that permit me to only begin coding the primary a part of my app. Issues like webpack configuration, getServerSideProps
, and Netlify’s computerized creation of serverless features are a couple of examples.
Netlify additionally makes deploying a Subsequent.js git repo tremendous straightforward. Extra on the deployment a bit afterward.
What we’re constructing
Principally, we’re going to construct a trivia recreation that randomly exhibits you the identify of a tennis participant and you need to guess what nation they’re from. It consists of 5 rounds and retains a operating rating of what number of you get appropriate.
The information we’d like for this software is an inventory of gamers together with their nation. Initially, I used to be pondering of querying some stay API, however on second thought, determined to only use a neighborhood JSON file. I took a snapshot from RapidAPI and have included it within the starter repo.
The ultimate product appears to be like one thing like this:

Yow will discover the ultimate deployed version on Netlify.
Starter repo tour
If you wish to comply with alongside you may clone this repository after which go to the begin
department:
git clone [email protected]:brenelz/tennis-trivia.gitcd tennis-triviagit checkout begin
On this starter repo, I went forward and wrote some boilerplate to get issues going. I created a Subsequent.js app utilizing the command npx create-next-app tennis-trivia
. I then proceeded to manually change a pair JavaScript information to .ts
and .tsx
. Surprisingly, Subsequent.js routinely picked up that I wished to make use of TypeScript. It was too straightforward! I additionally went forward and configured Tailwind CSS utilizing this article as a guide.
Sufficient speak, let’s code!
Preliminary setup
Step one is organising setting variables. For native development, we do that with a .env.native
file. You possibly can copy the .env.pattern
from the starter repo.
cp .env.pattern .env.native
Discover it at present has one worth, which is the trail of our software. We’ll use this on the entrance finish of our app, so we should prefix it with NEXT_PUBLIC_
.
Lastly, let’s use the next instructions to put in the dependencies and begin the dev server:
npm set upnpm run dev
Now we entry our software at http://localhost:3000
. We must always see a reasonably empty web page with only a headline:

Creating the UI markup
In pages/index.tsx
, let’s add the next markup to the prevailing Residence()
perform:
export default perform Residence() { return ( <div className="bg-blue-500"> <div className="max-w-2xl mx-auto text-center py-16 px-4 sm:py-20 sm:px-6 lg:px-8"> <h2 className="text-3xl font-extrabold text-white sm:text-4xl"> <span className="block">Tennis Trivia - Subsequent.js Netlify</span> </h2> <div> <p className="mt-4 text-lg leading-6 text-blue-200"> What nation is the next tennis participant from? </p> <h2 className="text-lg font-extrabold text-white my-5"> Roger Federer </h2> <kind> <enter checklist="nations" sort="textual content" className="p-2 outline-none" placeholder="Select Nation" /> <datalist id="nations"> <possibility>Switzerland</possibility> </datalist> <p> <button className="mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto" sort="submit" > Guess </button> </p> </kind> <p className="mt-4 text-lg leading-6 text-white"> <robust>Present rating:</robust> 0 </p> </div> </div> </div> );
This kinds the scaffold for our UI. As you may see, we’re utilizing numerous utility courses from Tailwind CSS to make issues look just a little prettier. We even have a easy autocomplete enter and a submit button. That is the place you’ll choose the nation you assume the participant is from after which hit the button. Lastly, on the backside, there’s a rating that modifications based mostly on appropriate or incorrect solutions.
Organising our knowledge
For those who check out the knowledge
folder, there ought to be a tennisPlayers.json
with all the info we’ll want for this software. Create a lib
folder on the root and, within it, create a gamers.ts
file. Bear in mind, the .ts
extension is required since is a TypeScript file. Let’s outline a sort that matches our JSON knowledge..
export sort Participant = { id: quantity, first_name: string, last_name: string, full_name: string, nation: string, rating: quantity, motion: string, ranking_points: quantity,};
That is how we create a sort in TypeScript. We’ve the identify of the property on the left, and the kind it’s on the suitable. They are often basic types, and even different sorts themselves.
From right here, let’s create particular variables that signify our knowledge:
export const playerData: Participant[] = require("../knowledge/tennisPlayers.json");export const top100Players = playerData.slice(0, 100);const allCountries = playerData.map((participant) => participant.nation).type();export const uniqueCountries = [...Array.from(new Set(allCountries))];
A pair issues to notice is that we’re saying our playerData
is an array of Participant
sorts. That is denoted by the colon adopted by the kind. In reality, if we hover over the playerData
we are able to see its sort:

In that final line we’re getting a novel checklist of nations to make use of in our nation dropdown. We cross our nations right into a JavaScript Set, which eliminates the duplicate values. We then create an array from it, and unfold it into a brand new array. It might appear pointless however this was accomplished to make TypeScript completely happy.
Consider it or not, that’s actually all the info we’d like for our software!
Let’s make our UI dynamic!
All our values are hardcoded at present, however let’s change that. The dynamic items are the tennis participant’s identify, the checklist of nations, and the rating.
Again in pages/index.tsx
, let’s modify our getServerSideProps
perform to create an inventory of 5 random gamers in addition to pull in our uniqueCountries
variable.
import { Participant, uniqueCountries, top100Players } from "../lib/gamers";...export async perform getServerSideProps() { const randomizedPlayers = top100Players.type((a, b) => 0.5 - Math.random()); const gamers = randomizedPlayers.slice(0, 5); return { props: { gamers, nations: uniqueCountries, }, };}
No matter is within the props
object we return can be handed to our React part. Let’s use them on our web page:
sort HomeProps = { gamers: Participant[]; nations: string[];};export default perform Residence({ gamers, nations }: HomeProps) { const participant = gamers[0]; ...}
As you may see, we outline one other sort for our web page part. Then we add the HomeProps
sort to the Residence()
perform. We’ve once more specified that gamers
is an array of the Participant
sort.
Now we are able to use these props additional down in our UI. Substitute “Roger Federer” with {participant.full_name}
(he’s my favourite tennis participant by the way in which). You need to be getting good autocompletion on the participant variable because it lists all of the property names we’ve entry to due to the kinds that we outlined.

Additional down from this, let’s now replace the checklist of nations to this:
<datalist id="nations"> {nations.map((nation, i) => ( <possibility key={i}>{nation}</possibility> ))}</datalist>
Now that we’ve two of the three dynamic items in place, we have to deal with the rating. Particularly, we have to create a chunk of state for the present rating.
export default perform Residence({ gamers, nations }: HomeProps) { const [score, setScore] = useState(0); ...}
As soon as that is accomplished, exchange the 0 with {rating}
in our UI.
Now you can verify our progress by going to http://localhost:3000
. You possibly can see that each time the web page refreshes, we get a brand new identify; and when typing within the enter subject, it lists all the accessible distinctive nations.
Including some interactivity
We’ve come an honest means however we have to add some interactivity.
Hooking up the guess button
For this we have to have a way of realizing what nation was picked. We do that by including some extra state and attaching it to our enter subject.
export default perform Residence({ gamers, nations }: HomeProps) { const [score, setScore] = useState(0); const [pickedCountry, setPickedCountry] = useState(""); ... return ( ... <enter checklist="nations" sort="textual content" worth={pickedCountry} onChange={(e) => setPickedCountry(e.goal.worth)} className="p-2 outline-none" placeholder="Select Nation" /> ... );}
Subsequent, let’s add a guessCountry
perform and fasten it to the shape submission:
const guessCountry = () => { if (participant.nation.toLowerCase() === pickedCountry.toLowerCase()) { setScore(rating + 1); } else { alert(‘incorrect’); }};...<kind onSubmit={(e) => { e.preventDefault(); guessCountry(); }}>
All we do is mainly examine the present participant’s nation to the guessed nation. Now, once we return to the app and guess the nation proper, the rating will increase as anticipated.
Including a standing indicator
To make this a bit nicer, we are able to render some UI relying whether or not the guess is appropriate or not.
So, let’s create one other piece of state for standing, and replace the guess nation technique:
const [status, setStatus] = useState(null);...const guessCountry = () => { if (participant.nation.toLowerCase() === pickedCountry.toLowerCase()) { setStatus({ standing: "appropriate", nation: participant.nation }); setScore(rating + 1); } else { setStatus({ standing: "incorrect", nation: participant.nation }); }};
Then render this UI under the participant identify:
{standing && ( <div className="mt-4 text-lg leading-6 text-white"> <p> You're {standing.standing}. It's {standing.nation} </p> <p> <button autoFocus className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto" > Subsequent Participant </button> </p> </div>)}
Lastly, we wish to ensure that our enter subject doesn’t present once we are in an accurate or incorrect standing. We obtain this by wrapping the shape with the next:
{!standing && ( <kind> ... </kind>)}
Now, if we return to the app and guess the participant’s nation, we get a pleasant message with the results of the guess.
Progressing by means of gamers
Now in all probability comes essentially the most difficult half: How can we go from one participant to the following?
Very first thing we have to do is retailer the currentStep
in state in order that we are able to replace it with a quantity from 0 to 4. Then, when it hits 5, we wish to present a accomplished state because the trivia recreation is over.
As soon as once more, let’s add the next state variables:
const [currentStep, setCurrentStep] = useState(0);const [playersData, setPlayersData] = useState(gamers);
…then exchange our earlier participant variable with:
const participant = playersData[currentStep];
Subsequent, we create a nextStep
perform and hook it as much as the UI:
const nextStep = () => { setPickedCountry(""); setCurrentStep(currentStep + 1); setStatus(null);};...<button autoFocus onClick={nextStep} className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto" > Subsequent Participant</button>
Now, once we make a guess and hit the following step button, we’re taken to a brand new tennis participant. Guess once more and we see the following, and so forth.
What occurs once we hit subsequent on the final participant? Proper now, we get an error. Let’s repair that by including a conditional that represents that the sport has been accomplished. This occurs when the participant variable is undefined.
{participant ? ( <div> <p className="mt-4 text-lg leading-6 text-blue-200"> What nation is the next tennis participant from? </p> ... <p className="mt-4 text-lg leading-6 text-white"> <robust>Present rating:</robust> {rating} </p> </div>) : ( <div> <button autoFocus className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto" > Play Once more </button> </div>)}
Now we see a pleasant accomplished state on the finish of the sport.
Play once more button
We’re virtually accomplished! For our “Play Once more” button we wish to reset the state all the recreation. We additionally wish to get a brand new checklist of gamers from the server while not having a refresh. We do it like this:
const playAgain = async () => { setPickedCountry(""); setPlayersData([]); const response = await fetch( course of.env.NEXT_PUBLIC_API_URL + "/api/newGame" ); const knowledge = await response.json(); setPlayersData(knowledge.gamers); setCurrentStep(0); setScore(0);};<button autoFocus onClick={playAgain} className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto"> Play Once more</button>
Discover we’re utilizing the setting variable we arrange earlier than through the course of.env
object. We’re additionally updating our playersData
by overriding our server state with our shopper state that we simply retrieved.
We haven’t stuffed out our newGame
route but, however that is straightforward with Subsequent.js and Netlify serverless features . We solely have to edit the file in pages/api/newGame.ts
.
import { NextApiRequest, NextApiResponse } from "subsequent"import { top100Players } from "../../lib/gamers";export default (req: NextApiRequest, res: NextApiResponse) => { const randomizedPlayers = top100Players.type((a, b) => 0.5 - Math.random()); const top5Players = randomizedPlayers.slice(0, 5); res.standing(200).json({gamers: top5Players});}
This appears to be like a lot the identical as our getServerSideProps
as a result of we are able to reuse our good helper variables.
If we return to the app, discover the “Play Once more” button works as anticipated.
Enhancing focus states
One final thing we are able to do to enhance our person expertise is about the concentrate on the nation enter subject each time the step modifications. That’s only a good contact and handy for the person. We do that utilizing a ref
and a useEffect
:
const inputRef = useRef(null);...useEffect(() => { inputRef?.present?.focus();}, [currentStep]);<enter checklist="nations" sort="textual content" worth={pickedCountry} onChange={(e) => setPickedCountry(e.goal.worth)} ref={inputRef} className="p-2 outline-none" placeholder="Select Nation"/>
Now we are able to navigate a lot simpler simply utilizing the Enter key and typing a rustic.
Deploying to Netlify
It’s possible you’ll be questioning how we deploy this factor. Properly, utilizing Netlify makes it as simple as it detects a Subsequent.js software out of the field and routinely configures it.
All I did was arrange a GitHub repo and join my GitHub account to my Netlify account. From there, I merely choose a repo to deploy and use all of the defaults.

The one factor to notice is that you need to add the NEXT_PUBLIC_API_URL
setting variable and redeploy for it to take impact.

Yow will discover my remaining deployed version here.
Additionally be aware that you would be able to simply hit the “Deploy to Netlify” button on the GitHub repo.
Conclusion
Woohoo, you made it! That was a journey and I hope you realized one thing about React, Subsequent.js, and Netlify alongside the way in which.
I’ve plans to broaden this tennis trivia app to make use of Supabase within the close to future so keep tuned!
If in case you have any questions/feedback be at liberty to succeed in out to me on Twitter.
Checkout extra Articles on Sayed.CYou
Comments
Post a Comment