Totalitarian State Surveillance with the React Native Debugger
In an earlier post, we mentioned the React Native Debugger. Here we’ll take a glance at one of its easy to miss features, the Dispatcher, which falls right out of RN’s use of Redux.
Redux is “a predictable state container for JavaScript apps”, which is a bit of an undersell. Porridge for breakfast every Tuesday, and filing your taxes in October, is predictable — but Redux is predictably awesome.
If you’re new to React Native or need to brush-up on Redux, the web abounds with tutorials. Briefly: Redux comprises three parts — ACTIONS, REDUCERS, and the STORE — that interact in a strictly defined way to let your app’s internal state change over time and its UI update correctly in response.
For example, internally your app might dispatch the action ListFetched
, which includes an array of items. That action is reduced to create a new version of the app state that now includes the fetched items, and sets the fetching
flag to false
. This new state replaces the old in the store. A spinner listening to the value of fetching
will receive the update and disappear, and a screen listening for new items can populate its list.
Beyond app correctness, this predictable flow of actions-to-reducers-to-store-to-updated-view(s) has other benefits. Code can be reasoned about, and put under test. And it allows you to travel backwards and forwards in time (Great Scott!), to replay actions processed by your app and observe their effect upon the view.
Time travel is cool, but would be cooler if you could do more than observe. That’s what the Dispatcher is for. It allows you to create an action and fire it into the Redux flow directly from the debugger. (But only as the most recent action, thus avoiding universe-ending weirdness.)
Why would that be useful? Let’s look at an example.
Just over a year ago Adapptor moved into new offices in the Perth CBD. In a piece of logistical genius, the old office was vacated Friday afternoon, and the new was ready Monday morning. But imagine if come Monday not all Adapptor employees were able to find the new office. What if years of dependence on smartphone navigation and a corresponding shrinkage of pigeon neurons (it’s Science), together with a citywide Google directions snafu, saw employees wandering lost on the city streets? Like most problems, this could be solved with an app.
Our app — let’s call it Panopticon — is a god’s eye view of the city, with the location of employees indicated by pins. On pressing the Search button, the map view dispatches the action, SearchLostEmployees
. If successful, this asynchronously dispatches a SearchLostEmployeesSuccess
action, including an updated list of discovered employees. The map view subscribes to changes in this list, and accordingly adjusts the map extent and pins.
Here’s the action dispatched by pressing the Search button. Note that it includes a default search radius of 1 kilometre:
{
type: ‘SearchLostEmployees’, payload: { location: { latitude: -31.949344, longitude: 115.8583657, timestamp: 1580777810932.208 }, distanceKm: 1, }}
And the action handed back by the network layer, SearchLostEmployeesSuccess
:
{ type: ‘SearchLostEmployeesSuccess’, payload: { Employees: [
{
Id: 3, Name: ‘Sawyer’, Location: '-31.949367478506957, 115.85831880569458', Image: 'https://adapptor.app/employees/3/photo.jpg' },
. . .
And here’s the resulting view:
Hmm, that looks a bit tight. Perhaps 5 kilometres would be a better default? We can test this without making any code changes using the Dispatcher. The simplest way to create the action is to select the SearchLostEmployees
action in the debugger Inspector, and copy the raw action. Then in the dispatcher window, located in the middle of the debugger’s left pane, paste the action and tweak the distanceKm
value to be 5. Now press Dispatch.
The action appears in the action timeline, and is handled like any other action created by the app. Here’s the new view:
This is a trivial demonstration, but imagine if the action you’d like to test would require many steps to create in-app, such as a complex form. Or perhaps the action data normally comes from a source that is difficult to orchestrate, such as a push notification. Maybe you want to play with edge cases that would stretch your UI assumptions. (Of course, you should probably consider putting these under test. The debugger’s Test feature can help you get started — it generates tests as before-and-after snapshots for a number of popular test frameworks.)
Anything in your app that can be triggered by an action, can be triggered by this side door. If you’re using the fabulous redux-observable middleware to implement epics, you could dispatch a single Refresh
to invoke a cascade of actions to affect your entire app.
The debugger is a surprisingly powerful toolbox. Take 5 minutes next time you’re coding to poke it.