Too much time is spent rewriting UIs

For many reasons, we just keep on rewriting UIs.

And usually, we tend to blame the tools, which is mostly justified!

Frameworks, libraries, package managers, guidelines… even languages and meta-languages themselves are changing so fast that there's little to no chance a common or standard way of building applications emerges.

Don't even talk about the testing tools and strategies; that could be the topic of an entirely different post. Who needs tests anyway, when you can SEE it's working. ;)

Most of the time, it doesn't matter: the UI gets shipped anyway, the customer is billed, and is billed again the year after when this necessary update will warrant a rewrite of the whole codebase.

There's a whole ecosystem of companies out there hiring fresh graduates, making them assemble a web UI as fast as possible using the latest trending tools, and selling it.

That is probably the biggest issue: no one cares about long-term support or maintainability of UI applications nearly enough to actually create the ecosystem that it deserves.

We can change that. We can help a standard emerge. We can focus on the topics that are consistently becoming an issue when writing web UIs and fix them; and we will do this thanks to years-old design patterns and coding strategies.

The 3 errors

What's best than a short example to get us started? OK, let's have a look at a code sample of a React component.

// FooComponent.js

import react from 'react'
import {ApiClient} from '../api_client'

var FooComponent = React.createClass({
  componentDidMount: function () {
    ApiClient.getTitle().then((data) => this.setState({title: data}))
  },
  handleClick: function (e) {
    e.preventDefault();
    bar_component.resetCoconuts()
  },
  render: function() {
    return (
      <div className="title">{this.state.title}/>
      <div className="button" onClick={this.handleClick}>
    )
  }
})

The example is indeed extremely simple, but that's intended.

Can you spot what's wrong in the code above?

Problematic imports and code flow

When the application logic is scattered all around your components or controllers, you quickly have no other choice than using hidden globals to build your code. Let's take the previous sample code and rewrite it in another way.

First of all, the ApiClient is imported.

import {ApiClient} from '../api_client'

This couples your data source with your component. This is almost never a good idea. There are at least 3 issues with this design:

  • Evolving ApiClient will most probably require changes in FooComponent.
  • Testing FooComponent will require a mock of ApiClient: HTTP backends, and so forth.
  • If you have 2 simultaneous FooComponent, the page will make twice as many requests.

But that's not even the main problem there.

This API client has not been initialized. What if it needs a base URL? (yes it does) A token? (it does)

Your component will need to feed those to the API client. Which means you have to pass all of those inside the component options. It breaks separation of concerns!

const myComponent = FooComponent.bootstrap('#anchor', {
    baseUrl: "https://xxxx",
    token: "MY_TOKEN",
    actualOption: xxxx,
})

This code needs at least two unnecessary arguments (the baseUrl and token) to work, that you'll have to mock when testing, etc.

When exactly did you have to pass URLs for a displayed component to work? It should only need data!

It relies on hidden globals

Secondly, the code relies on a global bar_component to handle click events and reset the coconuts. This is also really, really bad.

handleClick: function (e) {
  e.preventDefault();
  bar_component.resetCoconuts()
},

There is nothing in the imports to warn us that there's a need for a global bar_component property on the window implicit object.

By the way, not only is it global, but there's also a plethora of possible bugs if bar_component somehow gets defined (by your next teammate?) in the handleClick() function scope.

Now, on an arbitrary scale of annoyance:

  • Level 1: You can't use or test FooComponent without BarComponent.
  • Level 10: BarComponent isn't just a dependency, it has to be instanciated.
  • Level 9001: this instance has to exist in some global place, without any explicit, top-level, automatically-retrievable, import.

AngularJS users: the common way to get into the same situation is to inject BarComponent or ApiService inside FooComponent.

Sure, those still are mockable (in an angular-specific way) but they are nonetheless "globals" that have to be defined somewhere.

It still introduces coupling.

The data flow is unclear

An extremely common issue: data retrieval (XHR, JSONP…) is scattered all over the place.

var FooComponent = React.createClass({
  componentDidMount: function () {
    ApiClient.getTitle().then((data) => this.setState({title: data.comment}))
  },
  // […]
})

This, aside from all the coupling issues we've seen before, also means that you don't have a clear view of where and when your HTTP calls are made.

Even worse, some of your components may rely on outdated calls (the API may have changed) while other parts of your application are up-to-date.

If the comment property from the XHR result above changes, you will have to edit and fix it inside your component, which doesn't seem all natural, right?

This accumulation of bad practices can only lead to a rewrite in a near future. What you need is a strict separation of concerns. The only way to achieve that is to plan in advance, as accurately as possible, what your app will do and what the data flows will be.

And there are also paradigms. Let me introduce you to…

The "main loop"

Let's peek at one of the oldest design patterns ever used in programming.

Remember your very first lines of code: open an editor, create a main() loop, and output "Hello World" somehow.

It seems pretty basic, but in the video game and desktop application fields, the main loop is the backbone around which the logic revolves.

In the Windows API

Here's a sample block of code, borrowed from the Windows API samples:

LRESULT APIENTRY WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch (message)
    {
        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            TextOut(hdc, 0, 0, "Hello, Windows!", 15);
            EndPaint(hwnd, &ps);
            return 0L;
        // Process other messages.
    }
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
    HWND hwnd;

    hwnd = CreateWindowEx(
        // parameters
    );

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    return msg.wParam;
}

Old school, right?

Look at it: there's a WinMain function (main) that creates the application window, according to a few parameters. One of those is the WndProc callback, which is responsible for handling any kind of events: user events, repaint events, and so on.

One main loop, one event loop.

See? The whole Windows ecosystem: millions of applications, 20 years of existence, relies on a simple main function and event loops.

In the SDL API

The SDL API is mainly used to design video games. It's often seen as a lightweight OpenGL.

Here follows a very simple sample app:

#include <SDL2/SDL.h>
#include <iostream>


int main()
{
    SDL_Window* handle(0);
    SDL_Event events;
    bool end(false);

    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        SDL_Quit();
        return -1;
    }

    handle = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED,
                              SDL_WINDOWPOS_CENTERED, 800, 600,
                              SDL_WINDOW_SHOWN);
    if(handle == 0)
    {
        SDL_Quit();
        return -1;
    }

    while(!end)
    {
        SDL_WaitEvent(&events);
        if(events.window.event == SDL_WINDOWEVENT_CLOSE)
            end = true;
    }

    SDL_DestroyWindow(handle);
    SDL_Quit();
    return 0;
}

Different ecosystem, same thing. A main entry point that initializes stuff, and an event loop for user input and other things.

OpenGL applications are extremely similar; you can find many examples there.

Convinced? Initialization, and main loop. That is our point for today: almost every kind of UI was designed around a central loop of events sent by different parts of the application.

Why, then, is it that most front-end applications don't do the same?

Because we weren't introduced to this. We have been used to hack our way using jQuery, then gradually assembling Angular components, accessing global variables defined… somewhere.

Javascript application: Main loop model

We've seen very quick samples from the Windows API, and the more videogame-friendly OpenGL and SDL libraries.

When you think about it, a web interface is, in a way, a simple graphical application. It was simply made with more recent tools.

What if we made our applications look more like…

// main.js

import {ApiClient} from './api_client'
import {FooComponent} from './components/foo'
import {BarComponent} from './components/bar'

// Init the components
FooComponent.bootstrap($('#foo_component'), options)
const bar = new BarComponent(document.getElementByID('#bar_component')))

// Get the data
const api = ApiClient.authenticate(getTokenFromStorage())

api.fetchCoconuts(function (coconuts) {
  // Handle-based data passing
  bar.setCoconuts(coconuts)
  // Event-based data passing
  document.dispatchEvent(new Event('data_is_fetched', coconuts))
})

api.fetchTitle(function (data) {
  foo.setTitle(data.title)
})

// Event loop
document.addEventListener('foo:click', function () {
  alert('Foo component was clicked')
  bar.resetCoconuts()
})
// FooComponent.js

import react from 'react'

var FooComponent = React.createClass({
  handleClick: function (e) {
    e.preventDefault();
    // Notify other layers (simplistic, but works)
    document.dispatchEvent(new Event('foo:click'))
  },
  setTitle: function (str) {
    this.setState({title: str}))
  },
  render: function() {
    return (
      <div className="title">{this.state.title}/>
      <div className="button" onClick={this.handleClick}>
    )
  }
})

Look closely at the code above, especially in main.js.

What we got here is beautifully decoupled components. Neither FooComponent nor BarComponent knows how the data is retrieved.

They don't have to, and they don't care.

For all they know, the coconuts could have ben retrieved from a hardcoded fixture. What matters is that the bar handle exposes a simple setCoconuts() API function, which any application logic can use externally.

Events are broadcasted in the document (why not; you could also use an event bus) for the various components out there to catch. foo also visibly sends out click events, which the main application logic can listen to in order to wire a click on foo to a reset on bar.

This way, the components are truly decoupled; they can be reused independently, without having any knowledge of one another.

Only the main application loop should get app-specific code.

Side note: there are NO globals here. We leverage the power of closures in the main loop, but that's it.

Conclusion

The principles shown in this article are actually very simple. Use a simple code structure for your application, and stick to it.

Nonetheless, we've seen too many wrong design patterns (or none at all) in the nature to not at least try and do something about it.

Here at Polyconseil, we value battle-tested recipes like those. Did you know? We also use make instead of Grunt, gulp, broccoliHave a look!

Next time we'll talk about other paradigms, routing, and a curated list of the most popular gotchas we've seen here at Polyconseil and around!

P.S.: You'll notice that this notion of decoupled components bearing clear APIs, along with flat events cycling through a main loop is only one form of Flux