It’s been a while since my last blog post on building a basic trivia app using React with Redux-less global state — quarantine has been weird and time for personal projects ebbs and flows, but I’m back at it. When I last left off, I had set up global state using just React hooks and the Context API, and tested basic functionality by making an external API call and saving the result to my store. My next step is to set up some basic app usage with this store data available. My goals for this phase of development are to split the app into separate welcome and gameplay pages, and go from the welcome screen to the first trivia question when a user hits the “Play” button.
As I mentioned in the first blog, please keep in mind this is a basically-live chronicle of me learning and experimenting as I go. I’m publishing these pretty much as I wrap up each development phase; there’s some editing to keep things straightforward, and I’ll try to edit out any blatant errors down the road should they come to my attention, but I may make smaller mistakes or eschew best practices without knowing. These blogs are meant as a reference material for folks working on similar projects, not a tutorial.
Okay disclaimer over — let’s code!
Step 1: Split the app into welcome and gameplay pages
Now that I’m starting to build the app out, it’s time to break the code into components and out of
App.js, which will control the render of top-level components. To start, I move the existing code I have from
App.js into a
WelcomePage component and create a new
GamePage component. Eventually this will hold the multiple choice question interface, but right now I'm just checking that routing between views works correctly, so I'm just having it be simple text for now.
As before, I’m only explicitly including imports that are new/immediately relevant for the sake of space; the rest should be implied. I’m also trying to pick my packages judiciously; I’m using React Router to render top-level components, and Chakra UI to handle my display components so I can focus on the meat of the app’s functionality.
Step 2: Wireframe the gameplay page
Now I need to set up my gameplay page in its basic form. I’ll worry about answering and advancing questions down the line, right now I just want to make sure that the user interface displays all the necessary information and is configured correctly that I can build functionality on top of it.
I have an array of 30 questions, and need to show just one of them at a time. This means I need to add another value to my state, keeping track of which question the user is currently on. Chances are I could probably get away with just using the
useState hook inside the
GamePage component, but A) one of my main goals with this project is building familiarity with pure-React global state so I'm okay with a little added complexity if it means getting that practice, and B) this way might actually end up being simpler if I end up having to do more prop drilling than anticipated.
GamePage is a top-level component, I'm keeping the core logic there and creating a display component
TriviaCard to render the UI. I've already reorganized
WelcomePage in a similar way, having it render just plain text and an
ImportTriviaButton component, not shown here.
Step 3: Handle prop/routing errors
This works for when I start from the home page, but I find that if I refresh the page or try to go straight to the
/play route, I get an error caused by
questionObj not yet being defined. After a little experimenting, I came up with the following additions to
GamePage. This checks for
questionObj and redirects to the root path if it isn't set. It also uses a blank string for the destructuring assignment of
question, just as an additional safety measure to avoid a possible null error. Disclaimer that this approach worked for me, but there may be a more conventional way to achieve the same effect.
Step 4: Clean up the interface
I’ve also noticed that the game page itself includes HTML entities (like
" for quote marks). I could use
dangerouslySetInnerHTML, but this is a free, open-source API. Even if it's probably safe, it's still not worth the risk. There are multiple libraries that decode HTML entities, but like I mentioned I'm trying to limit my packages installed, so I'm writing my own solution. I create a dictionary-style object called
htmlEntities that matches the key of the entity code with the value of the actual character to print. I then write a function
htmlDecode() that takes a string and uses the
replace() method to sub in the correct characters. (This throws an error if
quetionObj isn't loaded, hence the second line). The category, question, and answer options are then passed through the function before being sent as props to the trivia card. Note this requires
let instead of
const in the destructuring assignment since the values are being overwritten.
Step 4.5: Realize you over-engineered a solution
Well folks, turns out that the Open Trivia API has multiple encoding options, including URL encoding that can be processed just with the out-of-the-box
decodeURIComponent() JS function 🤦♂️
I'm refactoring the app to use this much simpler solution, but still leaving all my prior work in this blog. A) It shows the real steps taken and keeps them around as reference for anyone working on a similar problem without a similar solution, B) it emphasizes the importance of reading your documentation, and C) it serves as a record of proof that, yes, these blogs are a live journal of my process, warts and all.
With that done, I’ve got basic trivia loading completed. For my next blog, I need to add functionality to progress through the questions sequentially and track correct vs. incorrect answers.