Posted on 18th October 2024

Writing sustainable code for apps - part 2: Interaction

Ever since the React framework came on the scene people started talking about user interfaces being reactive. It may come as no surprise that if you ask ChatGPT to summarise what the benefits of React are, it spews some non-sensical recursive text about how reactive React is.

If you're patient enough to read to the end of this post, you'll be rewarded by my own interpretation what 'reactivity' in UIs really is, explained in just a few words.

However, what I want to talk about here is the 'interaction' part of building an app (whether it be web, mobile or cross-platform*). Interactivity is the stuff that makes your applications feel alive, without which you are presented with a static user interface.

* I wonder if the term cross-platform app is reaching the end of its life. I think 'trans-platform service' is something that could have legs, but who needs more jargon in this industry? I'll take the chance if you're reading this you already know what I mean by cross-platform and also know that it is usually about something more all encompassing than just Android and iOS artifacts.

A static UI alone is something that has value - see my previous post on 'view' code where I conclude many applications would be better using plain HTML. This somewhat related to the idea of progressive enhancement, a principle that we would do well to be more mindful of when developing apps, myself included. However I didn't discuss progressive enhancement at all in my previous post since I don't feel confident that I know how to do it really well myself.

What is interaction in a user interface and why do we need it?

'Interactivity' in many cases means implementing some degree of high-level programming, usually in Javascript. The more you deviate from a simple HTML page / form (which again I stress can be a basis for most software alone), the more you will need to do some programming.

Interaction needs vary from a requirement for 'autocomplete', like that found on an address lookup component (a humble but hugely time-saving feature), to a full blown 3D visualisation of data that can be dynamically modified by the user. HTML might provide a backing for some of this, such as placeholders for content to appear, but it cannot provide the dynamism we need to implement these components* fully.

* On the web you can now indicate to the browser that a field should provide autocomplete options from the browser's saved information. You could also perhaps also have a solution for autocomplete that references another portion of HTML containing the items to be filtered. But if you want something you have full control over which relies on external data like an address lookup, you will need some code behind that.


A suggest-as-you-type field or autocomplete is a great example of the kind of dynamic interaction between the user and a system that can allow for a positive digital experience

Hopefully I've done a reasonable job of conveying the importance of dynamic interaction. It is worth noting the more interactive your app becomes the less accessible it likely becomes too. So depending on what we're building we still ought to consider limiting the interactive 'cleverness'. But the point is, it is sometimes necessary.

Types of interactivity

Some interaction will target and update elements within a pre-defined UI, such as an autocomplete. A web app implementing autocomplete might use a bit of JS to attach an 'event' to a text field. When the user types in the field, the event is triggered and then more JS code carries out the following: Performing a lookup, getting an element and injecting some content into it to display the suggestions. This is something if we really like to sound clever we call 'manipulating the DOM'.

Other interaction involves much more complex graphical display, like a 3D visualisation. In this case we can't really use the standard elements that would be defined in typical view code like HTML. Sometimes things like simple games might use HTML elements directly, but 3D charts or complex animated graphics usually involve programatically 'drawing' the visuals on a canvas. All the visual elements and user input are handled by the 'program'.

These are two distinct types of interactive components: On the one hand with the autocomplete we could use progressive enhancement to ensure those that do not have Javascript enabled (or perhaps are using an accessible mode) are still able to use the rest of the application. On the other hand with a complex 3D visualisation, there may be no way to really fulfill the function of the visualisation without it, although a substituted component - like a data table - could be offered.

Very often the lines are blurred and we create a dependency between the interactive components of an application and the overall functionality of the app itself. While not great and we should do our best to limit this, this is the reality of developing user interfaces.

What about sustainability?

The question I'm posing is: how do we develop interaction code that is sustainable? By sustainable I mean code that has endurance and doesn't require unnecessary re-writes, for example if the framework it is written with becomes obsolete. One obvious answer which holds true for all aspects of coding is 'don't use frameworks'. So if we consider a web app this would mean plain JavaScript and for mobile it would mean native app development.

Recapping on the last post, for increased longevity of the view's structure we should avoid tying the application logic and behaviour (our interactivity) to the view code defining the semantic structure. Basically, don't use the section that defines the view to do programming, avoiding writing functions or complex evaluations in the view e.g. Do {{ myDataBinding }} not {{ thing.doSomething(oneThing + anotherThing, oneMoreThing.somethingElse() ) }}

Sustainability depends on portability

The more fundamental problem preventing further sustainability of the code governing interaction is that of portability. Despite the industry having promised the ability to develop code in one language and run it in all environments, this has simply never been delivered in full, nor is it likely to any time soon*

* I believe solutions like the JVM cannot be considered fully universal if you can't distribute apps that use them on platforms like Apple devices. Until regulators gonna regulate, that's not going to happen. Also many corporate backed cross platform frameworks (which could be thought of as a modern write-once-run-anywhere approach) have not survived long enough that I would consider apps written in them sustainable. Maybe Flutter will be the exception, but I doubt it. C is probably the most long-lived and widely supported programming language but you still need to port and compile code explicitly for each target platform. And writing non-trivial apps in plain C is a challenge, though not as much as you might think if you've never written much C.

Manually porting programs from one language to another isn't too bad when you are talking about an individual function's logic without reliance on other code frameworks and libraries. But we definitely can't just copy our entire JavaScript program's code and paste it into a native programming language. Despite its prevelance even using JavaScript on all platforms is not yet achievable, nor is a universal programming language necessarily the right answer. Basically we've tried all this before and it's not worked out for.. 'reasons' (political / commercial / technical etc.)

You also can't really achieve the ability to develop encapsulated, reusable components that could be copied from one application into another without some kind of framework. This is a problem something like React can solve, but then your app will only be sustainable if you use React forever everywhere. It ties you in to unique the unique syntax and concepts that represent the opinions of the authors about how UIs should be developed. In short, good luck migrating your React app to something else when the inevitable time comes that you have to because you're going to need it!

A more enduring solution requires us to consider what it is we are usually trying to achieve in this 'space' that programming occupies.

Programming for interaction - what is it all about?

Programming provides a vast solution space but very often (when it comes to 'applications') we only need to work with a small fraction of the possibilities offered by it.

Programming languages are typically categorised in a few different ways, for example:

What is less common to consider is the applied area, from a user's perspective, that the languages can operate within. We could design and structure languages, or create guidance on how write code, based on types of the user experiences we are striving to achieve. Perhaps I'm scratching at the concept of something like 'design systems' for the programming of certain kinds of software.

In considering that most of the time we are developing for interaction in user interfaces incorporating familiar components, such as autocompletes and form validation, surely we can get to point where we can understand and define the true nature of the kind of logic we want to implement in a typical front-end. Then we can think about how to shape our code for interaction in a way that makes it more portable and generally sustainable.

An example of typical interaction logic in the front-end

HTML alone has come a long way in serving many of the more basic kinds of interaction needs. But consider for example a form which requires validation logic dependent on looking up values from a database and then performing a calculation. Perhaps the aim would be to determine if values entered are within a certain tolerance. If the tolerance isn't met a warning pops up, we highlight a value entered in a certain colour, or both.

Conceptually this isn't that difficult - it's a task that perhaps a human might perform manually when checking and approving a physical document submitted to them. Digital validation done right would certainly save a lot of time here. But it could be very expensive to maintain if it's locked into a specific piece of software, because of the framework we chose to use. What if we could define this kind of logic and behaviour in a fairly universal manner, without the need for 'boilerplate' or framework specific code?

It is not inconcievable to believe if we gain a better understanding of programming logic for user interaction and how to approach it we could definitely have a fairly universal approach to interaction logic to make it easier to write and more portable.

User interfaces are designed and operated within different contexts so a one-size-fits-all solution may not be appropriate. And it does get more complicated when we consider things like interactive 3D visualisations. But much like how it is frustrating we don't have a universal way of defining UI view code, it is also a shame we haven't gotten further in agreeing at the very least some of the principles for writing the code that governs the interactivity in user interfaces.

What's my solution?

By now it should be clear I'm not far down the road with my opinions on this. I don't have many suggestions of how to structure interaction code, nor a strong notion of 'things we should be looking for' in solutions.

I think this is a problem no-one has come very close to solving, but I will try to explore it and the issues here nontheless.

Consider something like a button:

<button onclick="alert('is this really a click, or a tap, or a press and does it really matter?" />

So we can just about agree a button is a thing, probably one that is useful on a lot of platforms in a lot of contexts, and that it is best that buttons are actually called buttons (although plenty of web apps still use links e.g. <a href="#" onclick="alert('I\'m a button, honestly I am!") />). But we can't even standardise what we call the event or how they should work.

I'm not saying the lack of standardisation of naming an input event is the most pressing issue when it comes to interactivity. Here I'm more concerned with what goes on inside that event 'function' and how we update the UI based on user input.

Some ideas of mine on defining UI interaction logic

I definitely think we should avoid programatically writing view code and attaching it to elements in the UI (e.g. myElement.innerHTML = "<h1>oh no, some HTML inside JS land</h1>").

Maybe a better way to create a button that makes some text appear would be something like:

<h1 #myMessage visibility="hidden">Oh that's better</h1>
<button action="show myMessage" />

Going further, let's see how we can dynamically retrieve some kind of result from data or perform a calculation without overcomplicating things:

<h1 #myMessage visibility="hidden">{{ state.result }}</h1>
<button action="myFunction myMessage" />

async function myFunction(StatefulElement e) {
 e.state.result = doSomeStuff();
 e.visibility = "visible";
} 

Things do get messier when we start talking about asynchronous functions, data binding and state. Governing what gets updated and when is a challenge. But I'm not sure the developer should really have to care about this most of the time. There should be implicit guard rails in place so someone writing interaction logic is protected from doing bad things that could have unintended consequences: I've not come across any framework that manages this in a convincingly effective manner.

Ultimately I'm just hinting at the kinds of conventions we need to be thinking about to write more sustainable code when it comes to interaction. My hope is that by getting it right there would be an obvious way to build certain types of interaction logic.

My previous post on this topic highlighted the perils of putting logic into the 'view' code so whatever the solution is I think we should be careful about how we bind interactive 'actions' to parts of the user interface. This naturally leads me down the path of considering placing constraints on what code you can put where and quite how much you let it do.

Establishing constraints and rules that enforce good conventions

I believe there is merit in limiting what you can put in something like a button's 'action' and what is capabilities are available to the author of the action:

In the above example a simple relation between the button, the function and the 'message' element is established (via it's 'action'). The simple nature of making the 'action' a relation that is limited to two elements has some validity. It seems somewhat declarative - a bit like simple 'facts' in Prolog*.

e.g.

/* Perhaps an action is a simple relation between 3 things: */ 
action_argument(buttonElement, messageElement).
action_function(buttonElement, myFunction).
/* Then the general rule for an action might look something like */
action(SourceElement, TargetElement, TargetFunction) :- action_argument(SourceElement, TargetElement), action_function(SourceElement, TargetFunction).

Of course this kind of 'grammar' is exactly how programming language syntax is constructed.

* At university Prolog was my guilty programming pleasure - but I make no apologies for the obscure reference to it here - I'm of the opinion if more developers learned some Prolog and / or its derivatives they would be better programmers. It seems particularly appropriate when we are talking about the logic-heavy world of user interface properties, behaviour and constraints.

So my nascent ideas involve placing more constraints on how you define functions governing interaction logic and possibly even constraining what those functions can actually do. Broadly, I think coding UIs should be a declarative endeavour. But it should be seamlessly declarative so that reading and writing UI code is easy.

I believe developers shouldn't be burdened by the inner workings of how the front-end is served. The framework (if one is required at all) should serve the developers and users, not the other way around.

A starting point that should be possible and beneficial would be to have some standardised syntax for these things so that they could be mapped to underlying functionality in whatever framework you choose. Think Object-Relational mapping but for translating front-end code to the UI framework / engine that serves it.

In summary

It is possible to build great interactive apps with the tech we already have - there have been many brilliant UIs built over the years and many dreadful ones too. But regardless of the qualities of an app's UX, when it comes to longevity and portability of the underlying code, we haven't made much progress, even though many of the fundamental concepts of a UI have existed for half a century now.

Things like buttons and even autocomplete have been around for an even longer time. In fact, when researching this post I was fascinated to learn autocomplete was a thing first invented by the Chinese in the 1950s to speed up the use of typewriters, pre-dating my own assumption that the command-line based autocomplete found in unix systems of the 1980s would be the first implementation of it.

We don't seem to have much of a handle on the best way to develop interactive UIs despite interaction logic being fundamental to so much technology we all use nearly all the time.

Big tech companies seem intent on reinventing front-end frameworks at great expense, rather than pushing forward progress on guidelines, best practice and principles of programming for UI interaction. Also, progressive enhancement seems not to be a staple of web development education and courses. These are significant contributing factors to the problem.

To some extent HTML and the web's evolution has 'organically' made gradual progress on this. To be fair to 'big tech' the general advances in front-end frameworks have made some in-roads to improve how we develop UIs. But what I really envision as progress in this area is front-end code that is truly easy to read and understand, with portions of user interfaces including the interaction logic that can be largely copied and modified in a 'plug and play' fashion.

For change to happen there needs to be more universally agreed and understood concepts in the development of UI logic and behaviour. This would help lessen the steep learning curve for front-end developers created by a situation where every framework takes a slightly different stance.

If you've ever thought "WTF is a 'promise' or 'hook' or a 'reactive effect'?" and "What was wrong with plain old events and handlers?" - you're not alone.

Some degree of agreement on the standards, principles and terminology used in front-end development would be nice, as long as it is not so restrictive it inhibits innovation of course.

What I feel we'll end up with, if we don't properly debate this, is JavaScript everywhere. Maybe that isn't such a bad situation and maybe it will eventually lead to more sustainable UI interaction code. Whatever the method, if a solution can be found that achieves this goal, I'd be happy enough. Because ultimately sustainable code is something I think is more important than reactive code, whatever that is*.

*(it's basically just data binding).