Separation of Concerns: Component Deconstruction

Breaking up your components into their functional pieces

Photo by Rick Mason on Unsplash

As a software engineer who works with modern web technologies (React, Node.js, etc.) and specializes in building front-end applications, I have encountered a common problem early in my career. The components that my team and I at Dictionary.com were making were often large classes with a bunch of methods attached and spanned hundreds of lines of code.

These components were hard to read and frequently had bugs introduced into them whenever they were touched. Testing all aspects of these components was often difficult because too many parts of the code had dependencies that were not clearly defined.

We decided that we needed to change our approach to the way we build our components and set out to deconstruct them. This would allow our code to be more readable and reusable.

Deconstruction…uh…what?

What do I mean when I say deconstruction of components? I am talking about examining your UI components, identifying the boundaries of concern within them, and then breaking the component apart where those boundaries of concern exist.

Live Example: https://codepen.io/techanda/pen/WaLXZG

I will use a simple UI component to explain what I mean. This component could probably be made more concisely, but I created it as an easy way to show you how to deconstruct a component. The above image shows the expanded drop-down menu button we will use. This component will expand and collapse when you click the button, and has a hover state.

const colors = {
primary: '#FD0A54',
secondary: '#F57576',
dark: '#23192D',
};
const DropDownMenuWrapper = styled.ul`
min-width: 160px;
display: inline-flex;
flex-direction: column;
list-style: none;
padding: 0;
margin: 0;
li {
display: flex-item;
padding: 10px 30px;
margin: 0;
background-color: ${colors.secondary};
:not(:last-child) {
border-bottom: 1px dashed ${colors.primary};
}
:hover {
background-color: ${colors.primary};
color: #fff;
filter: contrast(100%);
}
}
button {
border: 0;
padding: 10px 30px;
min-width: 160px;
background-color: ${colors.dark};
font-size: 16px
color: #fff;
outline: none;
:hover {
background-color: ${colors.primary};
}
}
`;
class DropDownButtonMenu extends React.Component {
constructor() {
super();
this.state = {
isOpen: false,
};
}
render() {
return (
<DropDownMenuWrapper>
<button
onClick={() => {
this.setState({ isOpen: !this.state.isOpen });
}}
>
DropDown Button
</button>
{this.state.isOpen &&
this.props.items.map((item, index) => <li key={index}>{item}</li>)}
</DropDownMenuWrapper>
);
}
}

The above code is what is used to make the component. For this example I am using React and emotion but the principles can apply with other frameworks, template languages, or just plain HTML, CSS, and JavaScript!

Why should you care?

The introduction of React and other front-end frameworks have made building a rich experience with web applications easier. These frameworks encourage us to contain all of the code for a component in a single location, making it easy to build a complex and reusable component quickly. All of this power has come with a cost: our JavaScript, HTML (JSX if you use React), and potentially CSS are all stored in one file.

How we think about our components and their boundaries has shifted. No longer are we separating our work by code type (Javascript, HTML, CSS, etc.). Instead we are creating boundaries around the functionality. This new paradigm allows us to contain the functionality of the smallest pieces of our applications in one place.

By approaching our component construction from a smallest functional piece perspective and separating our logical components from our user-interfacing components (more on this later), we maximize our ability to reuse these components in a much broader range of situations.

What is a UI component?

A user-interfacing (UI) component is one that is designed with the purpose of providing content or control to a user. Content can be a written article, a thumbnail image, a content card, or any other visual content that a user will see. A control component is also a visual component that a user sees, however its purpose is to be interacted with. A control component can be a button, a text input, or any other component that a user can interact with.

The following examples show the two UI components from our dropdown menu example:

A styled button (code below)
/* A styled button UI component */
const Button = styled.button`
border: 0;
padding: 10px 30px;
min-width: 160px;
background-color: ${colors.dark};
font-size: 16px
color: #fff;
outline: none;
:hover {
background-color: ${colors.primary};
}
`
A list menu (code below)
/* Style for the component */
const DropDownMenuVerticalWrapper = styled.ul`
min-width: 160px;
display: inline-flex;
flex-direction: column;
list-style: none;
padding: 0;
margin: 0;
li {
display: flex-item;
padding: 10px 30px;
margin: 0;
background-color: ${colors.secondary};
:not(:last-child) {
border-bottom: 1px dashed ${colors.primary};
}
:hover {
background-color: ${colors.primary};
color: #fff;
filter: contrast(100%);
}
}
`
/* A UI component that takes an array of items and builds the dropdown list */
const VerticalDropDownMenu = ({items}) => (
<DropDownMenuVerticalWrapper>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</DropDownMenuVerticalWrapper>
)

The button example highlights a simple component that is just styling and takes no unique properties for it to be used. This component can be used in a similar way as a normal HTML <button> .

The dropdown list example is a bit more complex and includes two pieces. The first is the styling that is provided by the <DropDownMenuVerticalWrapper> styled component, which is used by the actual component <VerticalDropDownMenu>. This component is a bit more complex than the button (it includes some logic) and accepts the single unique property of items that is mapped over (the logic!) to build the component. Still, this component is designed to accomplish one simple task: display a dropdown menu.

The takeaway from this is that UI components can consist of simple components that are purely visual like the button, or be a combination of simple UI components, as well as logical components. The important part is that they are written in a way that the smallest building blocks can be reused.

So what is a logical component?

So now that you understand what a user-interfacing component is, what exactly is a logical component? A logical component is a component that takes in information (props for React) executes some instructions against the information, and returns updated information. These components do not include anything that a user can directly see.

These components might return true or false when a user is hovering over a certain part of the page. A logical component might take an Array and update it, returning a new Array. One of my favorite uses for a logical component is one that handles requesting data from an API. This can be used to easily add an API cache to your application by having the response stored in the API Components state. Other things that you can do is modify an Array or track form data.

In our example, the logical component called <ClickMenu>manages the state of the dropdown menu. It has a state property of isOpen that stores whether the dropdown menu should be visible. It also has a toggleMenuOpen method that shows and hides the dropdown menu by updating the state.

class ClickMenu extends React.Component {
constructor() {
super();
this.state = {
isOpen: false,
}
this.toggleMenuOpen = this.toggleMenuOpen.bind(this);
}
toggleMenuOpen() {
this.setState({ isOpen: !this.state.isOpen})
}
render() {
const ClickComponent = this.props.clickComponent;
const MenuComponent = this.props.menuComponent;
return (
<ClickMenuWrapper>
<ClickComponent onClick={this.toggleMenuOpen}/>
{this.state.isOpen && <MenuComponent/>}
</ClickMenuWrapper>
)
}
}

A couple of things to note about this component. It uses Render Props to supply the UI components to the logical component. ClickComponent and MenuComponent are assigned by this.props.clickComponent and this.props.menuComponent respectively.

Photo by Johny vino on Unsplash

Bringing everything together

We now have all the pieces that we need to build our full dropdown menu component. <Button> will be theClickComponent and <VerticalDropDownMenu> will be the MenuComponent. How do we put all the pieces together?

<ClickMenu 
clickComponent={
props =>
<Button {...props}>
DropDown Button
</Button>
}
menuComponent={
props =>
<VerticalDropDownMenu
{...props}
items={items}
/>
}
/>

As you can see, it is pretty easy to put the pieces together. The <ClickMenu> logical component accepts functions (specifically stateless functional components) as the clickComponent and menuComponent properties, and uses them to build the fully functioning dropdown menu.

It is that easy! When you are thinking about your components you just need to think about them as smaller pieces. Create each of the pieces and then bring them together as a whole. This maximizes the reusability of your work and makes your code more readable.

Play with the code

You can view the pen here