Working on a suite of applications for a big enterprise, one understands the benefits of reusable components quite quickly. Chances are, at least 50% of functionality, like a user-friendly customer lookup, will be used in the majority of your applications with just a little bit variation in in how it looks or works case-by-case. As an experienced developer, you take a base component library and build your CustomerSelectorCmp on top of it up to the current requirements. It has all the necessary logic, it is used in 20 apps and it works great. Until new requirements come:

  • A new app is being commissioned, and CustomerSelectorCmp needs to have a horizontal mode for it; This will probably be used only in this 1 app;
  • Our managers have doubled sales numbers by grabbing tablets for client visits and having our app ready to go in the heat of the moment; But CustomerSelectorCmp could be much better with touch;
  • We have 10% of managers with special visual needs – it would be very nice to have accessibility mode for CustomerSelectorCmp;

5 years ago, trying to accommodate all this functionality in a single component would inevitably have to violate Single Responsibility Principle and lead to an unmaintainable monstrosity, where key CSS properties are overridden at-least 3 times each, classes are added just to increase CSS selector specificity and !important is your best and worst friend. Well, lucky we are in 2020! Thanks to CSS-Grid and React, we can have a base component that contains all the key functionality and can change appearance drastically with just a dozen lines of CSS and JS. See the following 3 dropdowns? They are the same component, same DOM! The difference is a dozen lines of CSS!

Grab the showcase project form github github and lets dive in.

In the sample project, we will see, how the same DOM can be made to have drastically different visuals. For starters, lets look at the Index.tsx . Notice, that it is using React fragments via “<>… </>” and essentially returns a flat list of DOM elements. You are probably used to relegating visual aspects of your apps to CSS and using HTML for content, semantics and structure. But we have found, that having visual structure correspond to DOM tree in HTML is too rigid – all limitation with no advantages. Our React components end up much simpler and yet more flexible, when we restrain them to simply deciding if a given DOM node should be present or not, but not caring about its positioning. For that we use CSS-grid. The accompanying Intex.less positions every example Select and it’s accompanying text. We've had flat lists of over 30 components positioned this way in a 24x6 by grid – and haven’t looked back once (even with IE11 support :-) ).

BTW, if you haven't read CSS-Tricks Complete Guide to Grid - you need to do it now. This guide has been indespensible in our daily work.

Now lets look at SelectBare.tsx This is the components, which defines the core properties for all our Selects and also renders our core Dom elements. Once again, you see a flat, simplistic DOM. But notice one more thing: our component is not concerned with its own container, label or id. In general – it tries to avoid responsibility as much as possible.

This is not because we want to keep example short, but because we keep responsibility single as much as possible. Does it make difference if we define ‘id’ on the container element instead of the component itself? Generally – no.

As for labels, between visual aspect and content aspects like tooltips, labels have enough requirements to warrant being their own component with their own positioning in the grid. Think about all the properties that may be required for label configuration – do we want to pile them into an already complicated component? And what about the fact, that every reusable component, DatePicker, Search, NumericInput etc... will likely have a label visually attached to it and requiring all the same configuration? Do we want to duplicate this code everywhere? No. In my expereience, we’ve achieved best results when treating Labels as their own independent component.

The general approach here is “less is more”. Whenever you are tempted to add a new property to your component – there has to be a very good justification for it. If we can split a components responsibility into its own independent compnent - we should do it.

Now, how do we get something that we can actually put into our app? Lets take a look at Select.tsx and Select.less . First – notice that Select reuses the properties interface of SelectBare. This will apply to all the Select's' we will be looking at. Select provides a container for SelectBare to make applying CSS styles and placing it easier.

Now look at `renderAdditionalNodes` property. This is an extension point for SelectBare. It’s main purpose is to receive internal state from SelectBare and render elements that will have access to that state, in this case – a toggle button. Notice an interesting thing – the toggle element is on the same level as core Dom elements of our SelectBare. Outside of inspecting React debug tools, we would be hard pressed to tell, that this are actually two separate components. Our Virutal Dom is ending up very simple – it is good.

The other three Selects showcase different combinations of CSS overrides and `renderAdditionalNodes`.

Middle Select shows how to ad-hoc override behavior with CSS – it opens upward with just 5 CSS properties overridden.


    #select-desktop-upward-opening {

        // override internal positioning a bit:
        // swap top and bottom rows, make items open up
        .select-desktop {
            //grid-template-rows: auto 0; by default
            grid-template-rows: 0 auto;

            .selected-item {
                //grid-row: 1; by default
                grid-row: 2;
            }
        
            .select-toggle {
                //grid-row: 1; by default
                grid-row: 2;
            }
        
            .selectable-items-container {
        
                //grid-row: 2; by default
                grid-row: 1;
        
                .selectable-items {
                    bottom: 0;
                }
            }
        }
    }

SelectForPOSTerminal.less demonstrates how to apply CSS to SelectBare to make it work well with touchscreens – when it opens, it covers the whole screen with big buttons (see gif above).

Finally, SelectRoller.tsx shows how we can use extension point of `renderAdditionalNodes` to completly change the visual structure of the component while still using its internal logic. Modification like this is to be used sparingly, but it is indespansable in corner cases.

Summary

Single Responsibility Principle has proven itself for decades. Now, finally, we have the right tools to separate DOM structure from visual structure. Adopting such a component to a new device format or corner case takes just a few lines of code and does not burden the core component with any more responsibility. With this approach, our components are easier to write, faster, less complex to maintain, and yet, more flexible than before. This is trully an upgrade.