App navigation is kind of like a pet hamster. It seems fairly innocent and unassuming, but if you don’t isolate it properly, it will soon start leaving its ugly marks all over your code.

Earlier this year, I took part in a project for a food delivery app. I had not been present at either the design nor development of the app itself, but was rather brought onboard for support work. This meant that I was able to look at the codebase with the advantages of both hindsight and an outsider’s point of view, giving me a twofold edge in being an absolute smartass about it. And my point of view was that navigation was a mess.

To be clear, I’m not trying to disparage any of my colleagues’ work. React Native was undergoing a lot of changes when development started, and people were still figuring things out; a unified navigation concept was probably the last thing on anyone’s mind. Nonetheless, there’s always value in looking back at how a certain task could have been handled even better.

“Tasks in the mirror appear easier than they were.” (Kalle Kortelainen, Unsplash)

So, follow along, and let’s see how you would go about this. Also note that this model is by no means limited to React Native; it’s a generic pattern that can be applied to any app that uses screen-based navigation.

The Problem

Use cases of our app consist of a few well-defined screen flows. When the user wants to place an order, they will start on the vendor selection screen, then go to the vendor’s menu screen, the order confirmation screen, and finally a successful result. Logging in and signing up also have their own flows, as do certain editing functions.

The basic order and authentication flows

The first catch is that screens can be part of multiple flows. For example, users are able to record a new delivery address both from the settings screen and the order confirmation screen. The app also has a list of dishes the user has recently ordered, along with a button that takes them directly to the vendor’s menu, with the selected dish already in the cart, effectively dropping them in the middle of the order flow.

Basic order, re-order and settings flows, with overlapping screens

Typically, all of this is handled by the screens themselves. If one screen is part of multiple flows, or if you need to start in the middle of a flow, you have to include all that branching and preparation of data then and there. You might separate the code into a view class and a logic class, but in the end it’s still the screen’s code.

Then, there’s preservation of data. As the flow progresses, each screen contributes an additional detail that needs to be stored for further reuse. In the order flow, the user first selects the vendor, then the dishes, then the delivery address, until finally all information is packed up and sent to the server in an API request. Up to that point, these morsels need to be collected somewhere.

Basic order flow with outputs of individual screens

You can try passing around a single object from one screen to the next (keeping in mind that different flows will collect different data, so have fun making that work in TypeScript); or, if the app uses Redux, you can push everything to a central store and make it accessible to all screens (essentially reinventing global variables).

Lastly, now that you’ve got all that figured out, here’s one final bombshell: create a version of the app that can be licensed to a single vendor. When the user starts an order, the vendor selection screen is skipped, and they’re instead taken directly to the dish selection. So basically, you need to change the behavior of every interaction that starts a new order flow. There’s one on the home screen, one in the hamburger menu, plus we automatically start an order flow when the user signs up, and… I think that’s all? Maybe? Eh, it’ll come out in testing.

You can’t say “QA” without “eh”. (Tom Sodoge, Unsplash)

Let’s try something else.