If you've entered this article because you think I misspelt the KISS principle, I'm sorry, but you've been clickbaited.
Let me explain myself.
The desing of reusable and flexible components has almost become a form of art (like any part of development actually).
However, it's easy to fall into the trap of creating overly complex components that are tightly coupled to specific use cases.
My take here is to keep them silly. That is, making them as dumb as possible. By doing so, we can maximize their reusability.
The key aspect of this paradigm is managing state from the outside. Instead of storing component state internally, we delegate state management to higher-level components that utilize them.
Let's take a look at how we can achieve that.
Managing state internally
Imagine we're creating a Dropdown component. The typical approach that many developers would take is as follows: when a user selects a field from the dropdown, we set that value within the dropdown component itself.
This way, we can set the component's state from the inside.
However, while creating the dropdown component with its internal state may seem sufficient for most scenarios, let's consider a different use case.
Imagine we want to reuse the Dropdown component in a filter page within our application. In this scenario, instead of setting the selected value within the dropdown itself, we would set a filter tag just below the dropdown when a user selects a field.
Now, our previous approach doesn't work. Some people might consider one of these options:
- Create another new component just for this new use case.
- Create a prop that indicates the component how it should behave.
Notice that both these two alternatives break the Open Closed Principle: any time we want our component to support one new use case, we'll have to modify it. None of those are a desirable choice.
Managing state with callbacks
The best approach here is to make our original component reusable by uplifting its state. But, how will we be able to set the component's state if we uplift it? Well, that's where callbacks come into play.
Rather than having a complex component manage its own state depending on its use case, we lift the state up to the parent component and pass down the necessary data and callbacks as props.
This way now, our component is opened for extension, but closed for modification
The dropdown component doesn't know what the callback does. It can set the dropdown value, set some filters, etc. It can do whatever we want.
The component just knows that it has to execute the callback. What the callback does is not its responsability. It's its parent's.
There are situations where we develop a component that we know we're not going to reuse. In such cases, applying this paradigm could potentially increase complexity. Therefore, it's important to use this approach wisely.
Instead of tightly coupling your components to specific use cases, consider composing them from the outside when necessary.
And that's why you should remember to Keep it Simple Silly, Stupid.
Top comments (5)
I argue against overly generic components.
I worked on multiple enterprise applications where the sole approach to front-end engineering was to "make it as generic as possible". This approach created unmaintainable front-end code monstrosities that took days on end to debug the simplest flows. Multiple engineers could not figure out where in the chain of state that passed through 20 or 30 generic components the bug occurred.
After surviving a few of these companies, I decided to strictly apply for Backend/Data/DevOps positions, and my life has been easier since.
The goal is to find a balance between over-generalizing your components to the point of technical debt or creating specific components for different scenarios. Some components should be coded for specific use cases, there is no way around that.
Edit: Next time please use code blocks instead of images, they are much easier to read !
Sure, I understand your point. The key word for me here is not generic: it's composition.
I found lots of components which are coupled to the use case they solve, which in some cases could work just fine (e.g. a very specific component you know that it's going to be used just in one place). But, If you have a component that you'll probably use in more that one place (e.g. a dropdown component), making it composable it's the approach that I'd recommend.
Thank you very much for the feedback :)
Right, I agree. The conclusion I think we both will agree on is that components that logically should be generic should be generic (e.g. a dropdown menu). With that being said, specific components should not be generic, and one should not chase generality as a development goal, but be logical, and efficient.
Like any worthwhile technique keeping components generic must be balanced against the opposite tension of suiting your code to the purpose it’s created to serve.
That is “everything generic” can be just as bad as everything bespoke. The wise software engineer knows how to balance those conflicting goals!
Saying “everything needs to be generic because we may need to reuse it later” strikes me as premature optimization. Creating code that’s easily reusable is a worthwhile goal but since none of us has a crystal ball to know how someone will want to reuse our code it can be a fool’s errand.
Sure! We need to take into consideration the specific case that we're dealing with (as I said here).
I don't see this case as a prepature optimization, but as another way to develop the component. Most of the time we avoid premature optimizations because they can lead to increased development time and complexity. In my opinion, this is not the case.
Thanks for the feedback! :)