Ian J MacIntosh.com

Mistakes I Made Building My First React App

I’m not an experienced React developer, but 2020 brought me an unexpected opportunity to learn. I was in Guatemala City, riding my motorcycle from Boston to Argentina when the Guatemalan government responded to the COVID-19 pandemic by closing all its borders and ordering all non-essential businesses to shut their doors. Stuck in my Airbnb, I looked for online courses to keep me from going crazy and found Wes Bos’s React for Beginners. After I completed it, I wanted to apply what I’d learned. I decided to write a game.

I remembered a game called Zoop that I’d played when I was a kid. It was an arcade-style puzzle game, sort of like Tetris, and while the gameplay was relatively simple, it was complex enough that making a clone of it would offer interesting challenges. I’d started to write a Zoop clone in 2018 using vanilla JS, but got distracted and stopped very early in the process. I used React to refactor what little code I had, and that set off a chain reaction of doing more and more development.

Even though I probably did almost everything wrong from a coding perspective, I didn’t stop, I just kept building. I added features one ticket at a time with little to no thought about the next ticket. I kept writing and writing and writing. When I was close to “feature-complete” for a first version of the game, I looked back at my code and saw I’d made a giant mess. It worked, but it was hard to follow and had poor performance, kicking the user’s framerate down into the single digits at points. I did it! I had successfully moved fast and broken things.

Being able to make a bunch of mistakes quickly is a luxury I don’t take for granted. In a professional environment, I put high value on stability and predictability. But I wanted to learn, and for me, that means making mistakes. So I made a lot of them. I want to share some of them in case they’re helpful.

Not Writing Tests

The biggest mistakes could have been avoided by writing tests from the start. I was so eager to “just start” that I skipped the test-writing and wrote functions that seemed to do what I wanted, but were difficult to unit test. I didn’t even know how to write unit tests for my components, so my “testing” process was: open the game in my browser, play it for a bit, and check to see if the feature I’d just delivered seemed to work right.

Implementing unit tests became one of those high-importance, low-urgency tasks that I kept pushing aside. Sure, I could learn how to use Jest to write unit tests, or I could just open my browser and see if things worked. I went with the option that got me an answer in one minute. It’s not obvious how time-consuming that cycle becomes when you’ve got random number generators responsible for putting the game into a particular state. When I set a launch date for myself, I knew I couldn’t spend time waiting for whatever specific thing needed to happen for me to be able to test something, so I had to figure out a faster way to make sure things worked.

My favorite benefit of following a TDD process is the architecture patterns it encourages. I find myself building a bunch of “pure functions” (routines that always yield the same output for a given input) because they’re so much easier to test. They’re also easier to read and reason about, generally leading to simpler applications. By waiting until the app was mostly written to add tests, I missed out on all of that. Instead, I had a mess of functions that were difficult to unit test, carrying multiple responsibilities with poor separation of concerns.

After I started writing tests, my development cycle got faster. Instead of having to leave my editor, play my game and wait for a game situation relying on my changes to arise, I could run tests and get an answer within seconds. I could still go in and confirm manually to verify everything worked as expected, but I didn’t have to refresh my browser and wait.

Poor State-Management

The “Board” component for my app includes four objects of four arrays of objects. A monster lives in a queue, a queue lives in a field, and a board has four fields.

fields: {
    up: {
        queueLengthLimit: 5,
        queues: [[
            {
                type: "monster",
                color: 0
            }
        ], [], [], []],
    },
    left: {
        queueLengthLimit: 8,
        queues: [[], [], [], []],
    },
    right: {
        queueLengthLimit: 8,
        queues: [[], [], [], []],
    },
    down: {
        queueLengthLimit: 5,
        queues: [[], [], [], []],
    },
}

All of this is stored in the Board component’s state, and React’s setState method only supports updating the highest level of state. So whenever a queue’s contents change, I need to make a copy of the whole fields object, update the copy with my changes (a fun refresher on passing by reference), then replace the old fields with the new fields object.

As queues fill up, this operation gets a little heavier. I suspect this causes some performance issues, as I noticed the framerate drop to single digits when the board got crowded, but I still need to do a proper performance review to identify exactly where the bottleneck is. At the very least, it seems clumsy.

That Board component holds a bunch of other stuff in state; there are so many child components that need to access the same data, so I just lifted everything up to Board’s state. I suspect a state management library like Redux would help clean things up, but considering I didn’t even implement tests until a week before shipping, it’s not a big surprise I didn’t add it.

Making Stupid-Big Components

Since I just kept on piling features on, my components got stupid big. I’m going to blame this on ignorance and my state architecture issues. So much was living in the Board component’s state (because it had to get passed between its child components) that I didn’t see any better option for where to put the routines to update that state than in the Board component itself.

I could have used some kind of getters-setters type model, passing setters down through props, then calling them from the child component responsible for handling the logic part. I’d love to see some best practices from other developers here, and suspect there’s a better option.

I was able to move some of the code for the Hero component out of Board, so that helped, but it was a drop in the bucket. My next steps would be: add more tests, decide on how to get my state issues under control, then refactor until I don’t have 500-line components hanging around.

Skimming Instead of Reading Articles

If you Google for some help with React, you’ll find a bunch of tutorial examples, StackOverflow answers, blog posts, etc. I picked and chose from those to get my app over the finish line. Some provided bad recommendations or were just plain incorrect. Looking back, if I’d read some articles from trustworthy sources start-to-finish instead of bouncing around all over the place, I could have probably saved myself some confusion and misunderstanding.

There’s definitely a balance between studying too long vs getting started too fast, and my “mash the gas” approach was definitely something new for me. On the other hand, if I’d spent more time reading about bad practices instead of learning about them through first-hand experience, I’d have missed out on valuable lessons.

Weak UX and UI Direction

At a certain point during development, the game was sort of playable, and I felt like I had to stop making everything look like little neon squares. It was time to make some design decisions, but I had no idea what I was doing. I decided before styling individual elements, I had to define the aesthetic I was going for. I wanted to be sure my project would have a cohesive look and feel.

At first I wanted it to have a similar feel as Spider-Gwen’s scenes from Into the Spider-Verse, so I made the menu hot pink and cyan, with lots of white. I have no idea why I wanted this. I started with the menu and was really happy with how much better it looked with just a little attention.

Then I switched gears and decided I wanted the game to feel like the ‘90s. Not like someone in 2020 trying to make something retro, I wanted to make something that looked like the earnest result of someone in ‘90s making something on-trend. I went to YouTube and watched ‘90s TV commercials. and jumped down rabbit holes of ‘90s design themes. Eventually I stumbled across a movement called Memphis design and it was a perfect fit.

Applying those treatments to the game was a lot of fun and made the game turn from a goofy side project into something that looked more like a real product, but by applying different styles (Spider-Gwen, Memphis, and filling in the blanks with examples from Zoop) I didn’t deliver a cohesive experience with a strong visual identity.

In Conclusion

After working this side project for months — a little over 80 hours by my count — I wanted to call it done. I picked an arbitrary launch date (“next Friday”), took an inventory of what important stuff remained and was realistic to do before launching, and got to work.

I don’t think I’ll ever be able to say it’s “done.” As of writing this, I want to add better test coverage, support high scores, correct some issues with the animation timing, change some UX stuff when each stage ends, and include a better introduction/instructions when the game begins. I want to make the design more cohesive. I also want to figure out how to make the game playable — enjoyable even! — on a smartphone.

I made a bunch of mistakes, but the game came out pretty close to what I had in mind when I started. Even if I were able to go back in time to start the project again, I’d happily make the same mistakes again to get the same experience. The real game for me was the process of making the game itself, using it as a fun opportunity to experiment with React. And project management, animation, sound engineering, and graphic design. I’m sure I’ll make a bunch more mistakes as I move forward with it, but I don’t know of any better way to learn.