How UI-driven State Increases Accidental Complexity
Tags: javascript, beginners, redux, architecture
Published: 2020-01-28 20:55:19

---
title: How UI-driven State Increases Accidental Complexity
published: true
date: 2020-01-28 20:54:53 UTC
tags: javascript,beginners,redux,architecture
canonical_url: https://evgenii.info/ui-driven-state/
---

 ![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/magdalena-kula-manchee-6dk_591MPPk-unsplash--1-.jpg)
### Short summary

The data-layer of your application (database, Redux state, etc.) should not have any assumptions about the interface.

When it does, it increases the risk of accidental complexity. As a result, each new change takes a disproportionate amount of time to implement.

* * *

### Disclaimer

I decided to focus on Redux because of three reasons:

1. It's popular
2. It allows me to demonstrate the problem with a minimum amount of code
3. It's surprisingly common to design a Redux-state with UI in mind so that the UI-elements will have to perform less data processing

The core principle remains the same no matter what stack you use.

* * *

## The problem

Imagine that you've built this simple task-manager:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/denormalized-ui-1.gif)<figcaption>Task manager with grouping</figcaption>

and now you need to change the UI a bit:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/redisign.jpg)<figcaption>The tasks shouldn't be grouped by projects anymore</figcaption>

How hard may it be?

Well, it depends. We can tell that **the problem is simple** , but we know nothing about how the system is organized. What if the code is so messy that we can't even touch it without risk of breaking something? What if we don't have tests? There are a lot of possible 'what ifs', and many of you might have seen the projects where adding one button takes days or even weeks.

You can see accidental complexity as a result of short-sightedness and previous mistakes which complicate all further work. Let's talk about one of the most common causes of it – UI-driven state.

* * *

## Redux-applications can be a good example.

Don't get me wrong – Redux, as a technology, is outstanding. It promotes immutability, separation of concerns, atomic use-cases and unidirectional dataflow.

At the same time, it gives developers flexibility to opt-out all these principles. And this is the place when all the magic begins.

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/horse_js-status-969267853206654978.png)<figcaption>The horse explains (<a href="https://twitter.com/horse_js/status/969267853206654978">https://twitter.com/horse_js/status/969267853206654978</a>)</figcaption>

The most of Redux-applications look alike. They have a similar file structure and reasonable test coverage. They use the same middlewares and same libraries to force immutability. The developers, who work on them, use the same devtools.

But in spite of all the similarities, the fates of these apps are entirely different. Some of them survived several redesigns and seamlessly, while others were abandoned or rewritten.

My limited experience says that the way you organize the state of your application defines its overall maintainability. How to make it right?

### UI-driven state? What is it?

One of the core components of any Redux application is **a store**. A store is the object that holds a whole state of your app, however big it is.

Just one object.

Obviously, there are several ways to organize it. Here is one example with two separate arrays for _projects_ and _tasks_:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/normalized-state-example-main.png)

and another, with _a UI-driven structure_:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/ui-driven-state-main.png)

One look on the second object is enough to understand how the structure of the whole application looks like. Most likely, it has a side-panel with the projects and the main region with all the tasks.

### Sounds good, but why not? 

At first glance, the second example looks much more appealing. You can model the structure of your application as a tree, implement dumb UI components, and that's it. Easy to reason, easy to debug, easy to prototype.

Do you remember the task-manager we planned to redesign?

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/localhost_3000_--1-.jpg)

Let's think about how it might be implemented. The UI-driven state would look similar to this object:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/first-tree-exaple.png)

All common use-cases, such as _adding, removing, or marking tasks as completed_ will have one thing in common – they all will change the object `state.mainContent.projectList`.

Let's have a close look at one scenario: _adding new tasks._

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/add-use-case.jpg)

What does exactly happen after we press the button "Add"?

Firstly, _the UI-component_ dispatches an action with the type _"TASK\_ADD"_:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/add-task-code.png)

Then, a corresponding reducer applies this action to the current state:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/add-task-reducer.png)

And it perfectly works!

We're good developers, so we even cover our code with unit tests to make sure that it behaves as expected:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/Screenshot-2020-01-20-at-23.24.10.png)<figcaption>Don't worry about 88.89% – it happens when you transpile Typescript to Javascript. The real coverage is 100%</figcaption>

Everything looks fine ...

### ... until we need to change the UI. 

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/redisign.jpg)

It supposed to be a simple redesign, but adding tasks doesn't work correctly anymore: we expect all new elements to be at the bottom of the list, but they're still grouped by project, even though we don't have visible nesting:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/wrong-order.jpg)

It happens because we still have a nested structure in our state, as the tasks belong to the projects. This is how it looks when we use the same reducers:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/task-5.png)

To fix it, we'll have to **change the shape of the state and all reducers which depend on it.** We need to change input and output formats for all affected reducers, which implies that we'll need to rewrite their tests.

One minute ago, we had 100% test coverage, and now we effectively have no confidence in our code.

* * *

## UI-agnostic state

In contrast, it doesn't happen with a denormalized, UI-agnostic state:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/normalized-state-example-main.png)

Tasks don't belong to projects, they all are kept separately.

If the interface needs grouping, we can implement it in the UI-level by using [Container Components](https://redux.js.org/basics/usage-with-react#implementing-container-components) which will map the state to the format, that UI can deal with:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/map-to-props-nested.png)

Similarly, we can support the new interface, where the tasks aren't grouped by project:

![How UI-driven State Increases Accidental Complexity](https://evgenii.info/content/images/2020/01/map-to-props-flat.png)

The benefits of this approach are huge:

1. We don't need to change any reducers
2. The tests are still valid
3. We can even support multiple interfaces if we need to 

* * *

## The last part

While it can be tempting to design your data-layer in accordance with the current version of an interface, remember that this is only the current version.

UI will change, it always does. You may want to run A/B tests or create a different interface for smartphones.

The last thing you want to do in this case is to reimplement the business and domain logic of your application with the risk of breaking it.

> Cover photo by [Magdalena Kula Manchee](https://unsplash.com/@magdalenakulamanchee?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/bite?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)