DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,873 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for UI? Piece a Cake
Anton Korzunov
Anton Korzunov

Posted on • Updated on

UI? Piece a Cake

One of the first false assumptions one could face during a long journey of becoming a developer, is that said journey is just about development, about you just writing some code.
Like - start a new project by writing code, and finish it in the same way.
Only later one will be told about testing, or the need to solve real customer problems, and other "business as usual" stuff, not sure which one came first.
It's fine to start your journey in this way, everything need a beginning, but this is not how it should continue.

This is not how it could succeed.

Our job is not about writing code, it's about writing the right code, writing it "Once and only Once", testing it, solving problems and finishing assigned tasks.

It's not about creating >new< things, 
but more usual about changing the >old< ones.
Enter fullscreen mode Exit fullscreen mode

Software engineering is programming over time. Engineering is about considering the long-term effects of your code. Both direct and indirect. Link

Read it this way - while moving forward think hard about what you are leaving behind what you need to make the next step.
πŸ’‘ Applicable to your live as well.

While the vast majority of information you can find out there is focused on how to "make" thing, let's talk about the future maintenance, about reducing different burdens - from the classical technical debt to cognitive load.

Let's talk about the multidimensional "Cake" approach, also known as Multitier architecture, also known as Onion Architecture, and how it is applicable to UI based applications.

Where is the Problem?

The problem is not only "where", the problem is also "when".

Let's imagine that you are working in a very agile project, of course you are, and you just bootstrapped a new application which already have experienced two pivots and going to have another one.

The essence of Agile is about being reactive to the changes.

It is absolutely ok to start a random redesign, it's absolutely ok to abandon almost complete feature and start redoing it in a little different way, it's ok to adopt for the new requirements and the only thing a developer should be focused at this point - be able to preserve as much as possible, about how NOT to start every time from the scratch. That's happens to all of us, but is not any efficient.

While the majority might understand the solution for a "Happy Live" as Pit of Success, where a well-designed system makes it easy to do the right things and annoying (but not impossible) to do the wrong things, it's still about making things(note "do the right thing"), not changing something existing, even something created yesterday (we "agile", right πŸ™„?).
I reckon the Solution for the change might have roots in the Chaos Engineering, where you have to accept that something will go wrong, a build a system resilient to it. While the essence of this philosophy is something you should always have in mind, there is another way to address the same problem.

Chaos? This what happen during(before or instead of) the family Christmas dinner, meetup and especially during Gala Concert. In all cases the path to success is to have some protocols, procedures and preparation.

Path to Success is a proper foundation.

Standing on the shoulders of Giants - a general concept that prior knowledge, and not only knowledge, could and should be used today πŸ‘‡

Using the understanding gained by major thinkers who have gone before in order to make intellectual progress

  • every time you use webpack, or any other bundler, and not create your own one - you stand on the shoulders
  • every time you use React, or any other UI abstraction - you stand on the shoulders
  • every time you use any library, not writing code from the scratch - you stand on the shoulders

The majority of developers would use some preexisting (third party) solution to solve their problem, would stand on the shoulders of other developers and "The Platform", but the same majority of developers are also missing the ability to stand on their own shoulders.

  • πŸ‘‰ every time you need to change something, there should be a giant you can rely on. You have to giant yourself.

A digital platform is a foundation of self-service APIs, tools, services, knowledge and support which are arranged as a compelling internal product. Autonomous delivery teams can make use of the platform to deliver product features at a higher pace, with reduced co-ordination. What is a 'Platform' anyway?

Shoulders

I've seen it

We will jump into some concrete examples shortly, but let's first create some concepts to act as a foundation, let's create our first small Giant, the one should know very well.

  • πŸ€– Terminator 1 -> 🦾 Terminator 2 -> 🦿Terminator 3. They all are backing plot of each other. Without the very first you cannot have the second.
  • πŸ“–Book (Hobbit) -> πŸ’Film (Hobbit, well 3 films). While there are many differences between the book and the film, they share the same foundation
  • 🀯Total Recall(1990) -> 🀯Total Recall(2012). Those films have nothing in common, except πŸ˜‰ the same foundation.

Every remake, every sequel or prequel, every film based on a book or a book based on film are the examples of Standing on the shoulders of Giants

3 spider mans

Which other Giants can exists?

Layers

Before you run away lets pick some example you definitely will understand. Probably it will be more correct to say - a lot of people by a some strange reason expect you to understand it, and once upon a time during every second interview for a JavaScript position you might be asked about this thing, and it always was not very clear for me, like it's 100% not related... until today.

The Layers of OSI Model, also known as a internet layer cake

OSI model

Hey! I said do not run away! Look how one layer of OSI stands on the shoulders of another.
There is no difference for you how the device you are reading this information from is connected to the internet - Ethernet, Wifi, 2G, 4G or 5G - it just works. The top-most(7th) layer is unbound from any network hardware.

Implementation details are abstracted into many layers and some of those layers are even interchangeable(WiFi!==Ethernet!==Cellular).


I hope you would like to experience the same smooth journey during UI development. Strangely, but often developers are trying to shorten processes, collapse layers and especially not separate concerns and trying to get something valuable from it. Again and again, with no Giants supporting them.


Well, might be using OSI as a example was a little too much, but

  • would you consider React as a layer?
  • would you consider webpack as a layer?
  • would you consider MaterialUI as a next layer?
  • and NextJS as one more extra layer?

For the User there is no different if an Application has been build with Nuxt, Next or bare webpack. For webpack there is also no difference if it is used by application directly or hidden inside Next.

Can you see all those giants, on the shoulder of which your application is standing?

If you ever wonder why your node_modules are so huge, better to say GIGANTIC -> that's it! πŸ˜…

node_modules


Another good example is Redux, as "Redux" by itself means nothing. It can be very different and you'll never know which recipe was used to bake it.

Redux+Thunk, or Redux+Saga provide a little more context for an expected taste of a given solution, however only RTK looks as a properly layered cake. Mmmm tasty!

The Whole and the Parts

Speaking of Redux, there is a very common "mistake" in the understanding of the difference between "Redux" and "Context API". To be more concrete - the difference between useReducer + React.Context API and Redux as the latter is technically the same as "Reducer + Context".
Right after React Context presentation many people, really many people, were wondering - πŸ€” do they really need Redux or what.

Well, probably they didn't, but the more proper way to explain what is wrong with such common and simple misconception is to refer to Weinberg’s Law of Decomposition, which states "the whole is greater than the sum of its parts".

Very easy to prove, just combine baking 🀯 soda and vinegar πŸ’₯.
Enter fullscreen mode Exit fullscreen mode

By the fact Redux is not only reducers, but also patterns, DevTools, and different middlewares for different use cases.
While Redux is ContextAPI + Reducer, it is BIGGER than a sum of its parts.

πŸ‘‰ the real work an engineer is to see such big wholes looking at the pieces. Some things are visible only from the distance

Cat geo-glyph

An interesting moment about said law is that is simultaneously states the opposite:

Weinberg’s Law of Decomposition is subtler. It says that if you
measure a system according to some gauge of functionality or
complexity, and then decompose it, and measure up what you
end up with, that the sum of the parts is greater than the whole.
Huh? This seems to contradict the Law of Composition.

The best way to read this to accept that you are never going to consume something in full, as a while, only the required pieces. And it will be always some stuff left unused.

Very easy to prove, just combine Cola and Whiskey 🀒
Enter fullscreen mode Exit fullscreen mode

Foundation: the Essence and the Variables

The very first step towards our goal is the ability to... left something behind. Separate flies and cutlets, extract placeholders from templates, split a single whole into the Essence and the Variables.

The best and the most common example for this are DSLs - Domain Specific Languages, including any Template languages, including React.

πŸ˜‰ is there any difference between <div>{{userName}}</div> and <User name={userName}>?

A very important moment is that the Essence/Variables Separation can be performed by:

  • moving the Essence to the Layer below (sinking functionality)
  • Variables would be "kept"(emerge) automatically, as you will need to find a way to configure the underlaying functionality.

This concept is almost equal to Software Product Line - methods, tools and techniques for creating a collection of similar software systems from a shared set of software assets using a common means of production.

This is also quite close to the Ports and Adapters(hexagonal architecture), where the "actual functionality"(Platform capabilities) is hidden behind Adapters (Essence in this case), which are in turn hidden behind Ports(Variables in this case).

To better understand, lets create a few examples:

Button Group

At many sites you might see Buttons positioned next to each other. Technically speaking they are nothing more that two Buttons placed in one parent and separated by some Gap. However, does it mean that this is what you should do?

const ActionButtons = () => (
   <div style={{display:'grid', gridGap:'16px'}}>
    <Button>Do</Button>
    <Button>Something</Button>
   </div>
)
Enter fullscreen mode Exit fullscreen mode

How many different way you know to create said gap, and how many different gaps you can use - 2px, 4px, 20px?
Probably said gap should be proportional to Button size to create a "coupling" between two buttons and let you use larger gap to create a "distinction".

This is why it's very important to create an abstraction - ButtonGroup

   <ButtonGroup /* don't think how*/>
    <Button>Do</Button>
    <Button>Something</Button>
   </ButtonGroup>
Enter fullscreen mode Exit fullscreen mode

Or even give underlaying logic more control over look-n-feel, and create an opportunity to collapse a few Buttons in one group into one Dropdown on mobile devices.

  { ifMobile ? (
    <Dropdown caption="Edit">
      <Button>Edit</Button>
      <Button>Delete</Button>
    </Dropdown>
   ): (
    <ButtonGroup>
      <Button>Edit</Button>
      <Button>Delete</Button>
    </ButtonGroup>
  // ⬇️⬇️⬇️⬇️
  <ResponsiveButtonGroup
    actions={[
     { label: 'Edit', action: ... },
     { label: 'Delete', action: ... },
  /> 
Enter fullscreen mode Exit fullscreen mode

Move one giant shoulder up. And there are so many reasons to have buttons grouped somehow, and all those use cases can be named to be used for a known reason!

Button groups

Table

Table is another example where second-abstractions can help you a lot.
Let's imagine you need display a table. You basically have two options:

  • render table by yourself
  • use some other library to do it

In the first case you might need to spend more time than needed to handle edge cases, implement virtualisation, sorting, you name it.
In the second case you might found any particular library to not matching your expectations in some details with no ability to change pre-backed solution.

Often in such case developers are picking the first case as the only possible, while that always need the second - some "solution" they can just use. It just has to be "as they want".
In the Component Approach such solution is known as a Component πŸ€·β€β™‚οΈ, no more, no less.

So, yes, go with option one, pick your way to render HTML, not a big deal, pick the way you do(if you do) virtualisation, pick the way to handle "table data" - there are many headless tables on NPM, and assemble in a way you need.
If one day later you will have another use case with slightly different requirements - create another Component, assembled in another way.
But it is important to have this intermediate abstraction layer, which states "this is how tables are made here", as exactly this point may change in time (redesign) and you want to avoid Shotgun surgery or Domino Effect. You want a single change to a single component at UIKit/Design system side, not any client code.

You want to stand on Giant Shoulders.

Modal

Modal is a combination of both cases above.

  • Modal itself should just provide you a ModalDialog functionality.
  • But the Application might need:
    • ConfirmModal, having a Cancel button and the Action button, next to each other in some particular order (depending on the operation system), with (probably) Cancel autofocused.
    • InformationModal, having only one Got it button
    • OperationModal to indicate some process and having no buttons.

Plus FileModal, which is not a "Design Primitive", but a separate experience with own rules and principles.

different modals

HTML Dialog Element -> JS Modal Dialog -> Use Case Dialog

πŸ€·β€β™‚οΈ We are ok to use window.alert and window.confirm, but almost no "UI-library" provide a second-abstraction over their modals to reflect the same functionality.

Modal is a lower-level construct that is leveraged by the following components:

  • Dialog
  • Drawer
  • Menu
  • Popover

(source Material UI)

If it is ok for you to use a Modal in some patterns, some of which are not look that modal, why not to create more patterns which are closely relayed to the Modal/Dialog, but represent particular use case?

Once you have a foundation - try to build something from it
and create a foundation for the next layer. 

Then try to build something from it.
Enter fullscreen mode Exit fullscreen mode

Middle-level architecture

So, UI is a piece a cake?
πŸ‘‰ Yes, it is if you think about it as about Cake. Layer on top of another Layer.

Are you already using Onion Architecture, where layers are separated?
πŸ‘‰ Of course. Look inside your node_modules, think how many other packages, libraries and layers are hidden behind the ones you know about.

There are High Level Architecture(read pictures) and Low Level Architecture(building primitives), what is this one about?
πŸ‘‰ And this is one is about something exactly in between - Middle-level Architecture, combining "some given blocks" to create Application according to HLA.
The one usually forgotten, and the one you always has to define for yourself by yourself.

Actionable advices

Take a single component and try to find another structure inside it. Find a Modal behind a Dialog, find a FocusLock behind that Modal, go to the very end on the left (atoms) and then go back to the very right(combinations).

Think in Atoms -> Molecules -> Organisms, not from Atomic Design point of view, but as a unidirectional complexity flow.
Remember the Table – you should be able having a Complex Component A break it into to pieces and assemble into Complex Component B. Then go back to those pieces and break them down.
πŸ‘‰ That is converting a single whole to the Essence and the Variables.

The point here - layers should not interfere, and should not be used in skip levels(Organism should never use Atom) that will enable their reusability and provide maintainability free from Shotgun Surgery.

Create a fancy cake, starting from More Generic layers and go to Less Generic ones.

portal cake

All a cherry on top.

Top comments (0)

Update Your DEV Experience Level:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. πŸ›