|<<>>|26 of 275 Show listMobile Mode

The UI is an afterthought, a detail

Published by marco on

Complexity: Divide and Conquer! by Michel Weststrate on May 7, 2017 (YouTube)

“Can we make our UI dumb enough to make our app usable without it?”

The video demonstrates navigating through a simple e-commerce site. Then, he shows how the app can be driven from the console by calling the APIs directly—upon which the URL and UI all update automatically. That is, the logic is not in the UI.

He then demonstrates that he can drive the web site without a UI by deleting the rendering to React DOM entirely. He can still manipulate the console API to perform the same operations because the logic is all defined completely independent of the UI. Of course, this is the same command-line interface that can be used in the automated tests, which means that the entire product can be tested without a UI at all.

I’m becoming increasingly convinced that neither React nor Angular is the way to go. Both React and Angular mix logic into the UI, putting the UI front and center. This is wrong. Additionally, Angular suffers from a complete inability to speed up the development lifecycle because it’s so strongly tied to WebPack.

I’ve used Redux before and the boilerplate becomes prodigious. I’ve used the React reducers as well, and it’s a bit better, but still doesn’t feel very natural. I’ve used MobX but long before its current incarnation where it really seems to “just work” as a store of state and reactive programming logic.

The when construct (see 16:37 in the video), which takes a predicate and an action, is a very neat concept that allows you to define exactly how your application reacts to state changes without burying it all in the components.

“If the view is to be purely derived from the state, then routing should affect state, not the derived component tree.”

Therefore, a url-change is an action like any other, modifying the state and letting MobX handle notifying all interested parties. Once you’ve gotten that far, you don’t even need a UI-specific routing library because you can just configure any router to direct URLs to the store API—which will automatically update the UI. The UI (e.g., React) doesn’t have to have anything to do with routing. A route change triggers an action, which changes the state. The UI reacts. The UI does not do anything with the route—it just triggers actions.

A reactive non-UI component ensures that the route stays in-sync with the state by reacting to changes in the state. In most cases, you can just create a value that calculates what the URL should be, based on the state. This could get complicated, of course, but it’s also completely separate from the rest of the application logic and can be thoroughly tested. We can also use the when construct outlined above to simply listen for changes to the calculated URL and update the browser’s location and history. This way, the management of the history and URL is not entwined with the rest of the application logic. It’s just reacting to state changes, like everything else.

Working like this results in automated tests that work naturally and look very much like Playwright tests—but completely without UI and using semantically meaningful constructs. The UI is an afterthought (as Michel himself wrote in 2019). Playwright is nice, but it’s a last resort when you’ve already botched the job of writing your code in a more testable manner. It’s a nice check that the UI is properly wired to the logic of the application, but should not be used to verify application behavior—simply to verify UI behavior.

This all goes very much in the direction of The Humble Dialog Box by Martin Fowler in 2002, which shows that we’ve known how to build software correctly for over 20 years—and we keep getting distracted by “the new shiny”, thinking that we can somehow start with the UI and still get maintainable software.