Skip to content
The Pursuit Of The Perfect Component
Dev·

The Pursuit Of The Perfect Component

Matan Sanbira
by Matan Sanbira
The Pursuit Of The Perfect Component

A component-based web app is not a new idea. Most of what we see today in our browsers is written in some framework like React, Angular, Vue, etc. So pretty much any front-dev today knows, or should know, how to code using this method. And every good developer wants to write that perfect component, which is simple, elegant, and always works.

In this article, I want to share with you how I, as a front lead at Loadmill, approach my component writing, Specifically how I create and evolve them. The examples will be in React, although I’ll focus more on guidelines and principles that I follow, than on the code itself.

Where do I start?

There are three main instances where you have an opportunity to create a new component:

  • when a new non-existing feature is needed.

  • when a code snippet is repeated in several places.

  • when a component is outdated and needs a redesign.

When presented with these opportunities it’s good practice to take the time and understand if a new component is needed. As a general rule of thumb, what should guide you to create a new component is -

Reusability — if there is reason to assume the app would require using the same piece of code in more than one or two places, currently or in the foreseeable future, it’s time for a new component. After all, we as developers always prefer to change something in one place that takes effect everywhere it is used.

File length - when the code gets too long, break it up and create a new file with a new component. But be smart about it, you don’t want to work on 5 different files simultaneously as you don’t want to scroll 500 lines to get to the code you want to change. The answer is somewhere in the middle, it’s not binary.

Different Logic - if part of the code needs a separate set of states and/or methods, consider separating it to a new component with the logic contained within itself.

Come with a plan

It’s always best to plan ahead and know what the component needs to do and what it would likely require in the future. If the component is small or straightforward, doing a once-over in your head of how it should be written can be enough, but for a non-basic component, I advise you to get out the old pen and paper and write down the core features, structure, and methods you’ll need to develop. Using a reference point, like a good component from your codebase or something you found online will help you understand how you can and want to create your new component. Maybe if it’s not a complex and logic-heavy code, a to-do list will help you keep track of what needs to be done and will remove the need to store all the main structures in your head while drilling down on a specific part.

In my example, I will show a setup dialog component that contains a type of form and a submit button. The feature is setting up a user’s test variables before running it.

Make it from scratch

A good developer knows the great power of ⌘+C ⌘+V, but writing something which you understand how it works, rather than just knowing it works, can have a big effect on your approach to coding. It will make it easier to change, refactor, and debug. From the import line to the states and the life cycle functions, knowing how the framework is built is to eliminate a lot of guesswork.

how all my component writing starts

how all my component writing starts

Clear props — Clear code

Naming variables and functions are one of the hardest things a developer does on a daily basis. A component is basically a function with a lot of variables… A good implementation of a component can be seen by the type and names of its props. You want your coworkers to understand what the component does and how to change it without digging inside the code and reviewing its inner workings. Simple and descriptive names (preferably with types) will make your component easier to use in new and different places in your app.

look at the “SetupDialog” use case and see if you can understand generally how it works

Look at the “SetupDialog” use case and see if you can understand generally how it works

The power of default values

Something I learned by looking into big component frameworks (like Material-UI or React-select) is that a good, flexible component can work with as little as zero props but can be customized with a variety of optional props. To accomplish that flexibility try to make as many of the states and props of your component optional, and with a default value. The default value can be as simple as false or null or as complex as a placeholder text or settings object. And my favorite, all arrays default to an empty array.

important to note that unless you need a super flexible component understand what props are significant for the logic to work as intended and don’t make them optional. Especially if you work with Typescript.

“submitButtonText” is not crucial for the component’s logic so it can be optional

“submitButtonText” is not crucial for the component’s logic so it can be optional

A component as a black box

Figure out what logic can be handled inside the component itself, and put it there. Writing a smart component that works with fewer outside props can mean multiple implementations with fewer duplicate code. This doesn’t mean you need to compromise the flexibility of the component, you can still make an optional prop for when you will need a customized solution.

handling the state changes of “testId” and “userNumber” can be inside the dialog component, and be sent back as an options object when submitted.

Handling the state changes of “testId” and “userNumber” can be inside the dialog component, and be sent back as an options object when submitted.

Everything can be a boolean

Sometimes, a new feature requires us to take an existing, naive component and add another layer of logic to it. This can happen with the simplest of components, even those with just a few, straightforward props. This is a great time to rethink your component — do you need to add another prop or take a more simple prop that’s already there and use it differently? For example, a boolean can be changed into a string type. By checking if the string is empty you can keep your boolean logic and still use the string value for further specific logic.

In our example, we were asked to make a parent entity named suite that contains an array of tests, so the user should be able to run tests of specific suite.

we can use the fact that the user should be able to pick a suite to run to evaluate if the dialog should be open.

We can use the fact that the user should be able to pick a suite to run to evaluate if the dialog should be open.

or/and we can evaluate the presence of the “testSuiteName” both as an “isOpen” boolean and as a dialog title.

or/and we can evaluate the presence of the “testSuiteName” both as an “isOpen” boolean and as a dialog title.

Get creative and try new things

Let's say you write great components that get the job done, but in the back of your mind you think “maybe there's a better way to do that”. I suggest from time to time to think outside the box and try something more creative. It can be unique code you saw somewhere online or at a conference or an idea from the back of your head. Go for it, if it all fails you can always revert the change, but you learned something new about what is possible.

For example, if we take our simple setup dialog component and say it needs to be implemented in more places in our app working with entities other than suite or test. Therefore it needs to be more generic.

I’ll show you the more simple naive component I wrote so far, and a more complex module based one.

the simple straightforward component I have writen so Far.

The simple straightforward component I have writen so far.

the module-based component receives a generic “entity” prop instead of the tests of a suite, and a “formFields” array to specify which setup fields are needed for each case

The module-based component receives a generic “entity” prop instead of the tests of a suite, and a “formFields” array to specify which setup fields are needed for each case.

the fields modules are written in the same file and are working with the same set of props that make them as interchangeable and generic as possible.

The fields modules are written in the same file and are working with the same set of props that make them as interchangeable and generic as possible.

for our test suite implementation, the component instance would look less simple to understand but still readable.

For our test suite implementation, the component instance would look less simple to understand but still readable.

That is not to say that one solution is better than the other, which leads me to my final point -

No component is truly perfect

Nor should we, as developers, always seek perfection. Yes, we should try to write tight, bugless, simple, scalable code, but code is inherently flawed by nature and design. If a perfect component existed, changing it wouldn’t be possible, and change is good. An existing perfect component will make all our other work look lacking, and it’s far from it. We should always make mistakes, and learn from them, share our knowledge, and open our ears to others, experiment, simplify, and refactor. I know when I’ll look at this article a year from now I’ll have better and different ways to write a “perfect” SetupDialog component. And that's really my point.

Now go and write your component, don’t worry if it’s perfect. 😉