4 Rules of Simple UI Components

Too often have I seen simple web applications become a huge pile of intertwined, indecipherable JavaScript code. As features are added, code is added into seemingly random files, event listeners are accidentally duplicated, jQuery selectors stop working because someone moved the DOM around one too many times and sooner or later you start to feel the consequences.

It fails randomly.
It breaks in strange places.
It has regression bugs.
And no one wants to change it (for fear that it will become their code!).

Chaos and pain ensues. From my experience, the code becomes complicated way before the interface becomes complex. I know very few truly complex user interfaces, but the internet I browse is an endless stream of complicated JavaScript code. Sometimes, I look at the source code just to amuse myself. Sometimes, I even do so before applying for a job. Uh oh.

How did the web become a breeding ground for both simple interfaces and complicated code? To make things worse, this complicated code is located in a codebase that changes all the time! It is very hard to get an interface right in a one swift commit. It requires successive refinement based on customer feedback. Although this code changes all the time, it is often the hardest part to maintain.

Maybe it is our infatuation for cool new JavaScript frameworks that led us astray, but we seem to have lost basic software craftsmanship practices.

Corey Haines wrote an awesome book called Understanding the Four Rules of Simple Design based on Kent Beck’s 4 rules of simple design, which I highly recommend. Inspired by this book, I tried to distill UI complexity and found 4 rules of simple UI components that should help us write better, simpler interfaces. This is nothing new, but somehow it seems to have been forgotten along the way.

Rule #1: Break it down into named components

Thou shalt not underestimate the power of good naming

Break it down into named components

This is the simplest, yet most powerful rule of software design. Whether it is to discuss with the client or to understand each other when saying “the thingy” broke, good naming has the virtue of bringing people together. Picking a good name today might even help your colleague better decouple code in 6 months. This is what Corey Haines calls behavior attractors in his book. It is easier to see where your new code should go when things are properly named.

For UI design, this boils down to sitting with the client and figuring out a name for each thing. What is this portion of the page called? This new kind of input, what should we call it? Name small things, agglomerate them, then name bigger things! If you want, you can follow principles from Brad Frost’s atomic design, which is a good starting point. Instead of saying “the blue bar in the viewer thingy is overlapping with the other side”, you could say that “the document toolbar in the patient history viewer is overlapping with the annotation explorer“. Once the vocabulary is agreed upon, this makes your code less prone to bugs and misunderstandings with the customers.

In order to name components, you must break them down to a manageable size. If not, you maybe end up with names that include multiple “and’s” or end with “Util”. Be careful to name components according to their level of abstraction. If you name something “Intervention Plan”, I expect it to be high level and be broken down into smaller components. Maybe an “Intervention Plan” is made up of “Observations” and “Directives”. Maybe “Observations” is made from an “Action Bar” and an “Observations Grid”. Notice how “Observations” and “Observation Grid” differ in their level of abstraction, this is important.

If you need to spark your imagination, I suggest you read this article from J.B. Rainsberger.

Rule #2: Do not assume your context

Thou shalt not care to know who thy DOM parent is

Do not assume your context

Now that you have components of decent sizes and with good names (don’t worry, this takes time to master), you might realize that there are far more opportunities for reusability than you ever envisioned. Read the remainder of this article to be ready for this!

DRY (don’t repeat yourself) is a good principle to adhere to, but components are not reusable by default; they must be made that way. The first thing you must be careful about is to make your components as context-agnostic as possible. After all, how reusable can a component be if it requires a specific context? Picture 40 components requiring 40 different contexts. Are you crying yet? If not, just know that in most cases, those contexts are neither obvious nor documented.

Do not worry, there is a simple way to prevent this! First of all, simply do not make use of your DOM parents or siblings! Imagine your components as having a “root” DOM node. You are not allowed to go upwards in the DOM past this point. Until web components are finalized and included in all major browsers, you may have to use discipline not to cross this boundary, but this can be done!

Now, this raises the question: how can we update a sibling or parent component? Think about it. It is OK for the parent to know about its immediate children, but a child should not know about its parent, yet it has to send a message to its parent. What pattern comes to mind?

There are a few acceptable solutions here, but one that is childishly easy to implement in JavaScript is an observer pattern. Call it observer or listener, for our purposes it serves the same goal: the child exposes a couple of listening points (say, onError, onNewDirective) and the parent registers a callback for it. Now you get components that do not need to know where they are included in the DOM or where in the DOM is the component they need to update. Awesome!

Rule #3: Give complete privacy to your children

Thou shalt not covet thy children’s DOM

Give complete privacy to your children

So far, we have named components that can be included anywhere in the DOM. Okily dokily! This is all fine and dandy, but chances are you will need components that are made from other lower level components (again, refer to atomic design for inspiration here!). That’s a good thing, but there is one strict rule: do not query your children’s DOM! I mean it. This is where most ripple effects start! You change one thing in that observations grid and somehow you end up breaking something else without noticing it. Or you do notice it and spend the next hour fixing all those places where they used that CSS class you just removed. Ouch!

This is fairly easy to avoid. However, as I said before, until web components are finalized, you may need to do this out of discipline. The reward you get in return is very high.

We now have clear boundaries for what a component can and cannot do. You are not allowed to explore the DOM upwards past your root node and you are not allowed to drill down in the DOM past your children’s root nodes.

This is nothing new. This is what encapsulation is all about! This concept has been around for as long as most of us can remember, yet we sometimes forget the consequences of not respecting encapsulation. Let the action toolbar know whether buttons are arranged with an <li> or a <div>. Instead, just call .addButton(myButton) on it. Do not interfere with the action toolbar’s affairs and let it handle its own buttons: it is its job! Now you have no reason to have duplicated jQuery selectors! All ripple effects from changing the DOM structure should now be contained within the boundaries of the component that manages it.

Oh, one last thing before one of you tries to hack this! Creating a method .getButtonElement() is not the way to go! Remember Tell, Don’t Ask? If you communicate with your parents using events, you communicate with your children using plain old message passing (i.e. method calls).

Rule #4: See the world from what you are given

Thou shalt assuage with what hast been given

See the world from what you are given

This is not a rule specific to UI components, but it is of crucial importance in JavaScript. Mainly this is aimed at all of you still using global variables everywhere, I hope you hit your toe on some piece of furniture!

You could state the rule this way: if a component requires a certain object to complete its work, it should be explicitly given to it. The only exception to this is if it is your responsibility to create said object. Since you have good names, it should now be obvious whether or not it is this component’s job to create the object. Just keep in mind that the factory pattern exists and is easily achieved in JavaScript.

Luckily, this is easily fixed in most codebases. Remember that your goal when refactoring is to work in small increments and keep the tests passing (you have tests, right?). If a component uses a global variable, just turn it into a method or constructor parameter instead. Then, whoever uses that component just passes in the global variable. Now fix those higher level components too. Rinse and repeat until you find the point of origin, then make an educated decision about where the object should reside.

This may seem complicated, but it is not. Now your components have explicit dependencies instead of requiring an implicit context. You improved the readability and reusability of your code, you should be proud! You will thank yourself in six months time.

As an added bonus, you just got rid of global variables which are a nightmare once you add concurrency. So far, JavaScript has been single threaded, but this will change. Better be ready than sorry!

What you should take home from all this

It sounds simple, doesn’t it? It should. Find good names, use events to decouple from your parents, delegate to your children and respect encapsulation, and make dependencies explicit. That’s it! This is not new nor ground breaking, but it should get you started on an happier path. Most of these principles are simply based on known best practices like SOLID principles or atomic design and none of this is web-specific. User interfaces have been around for ages and I think web developers should read more about the accumulated knowledge drawn from these years of experience.

The even better news is that it is fairly easy and fast to go from a humongous entangled to a structure of well defined and well behaved components. It is out of the scope of this article, but I might write about this at a later time. I will also show how to do it at my conference for Agile Québec on January 20th (in French). In the meantime, you can see a working example of this on my github repository. You will find 3 examples: one is a huge JavaScript file, one is a rewrite using React and the last one is a rewrite using only RequireJS.

Oh, also, here is one last bonus for you! You now have components that are trivial to test! Try it out, you might be amazed.