How Inversion of Control can Help to Keep Code Clean and Reusable

Design patterns can greatly improve our code quality and reusability, here's how we can use one of them!

Laptop colored

As developers, we all strive to create clean and reusable code. However, as we keep adding more features, our codebase can quickly become messy and difficult to maintain. That's where design patterns come in handy. One such pattern is the Inversion of Control (IoC) pattern.

Let me share a story to illustrate how IoC can help you create more flexible and reusable code.

Meet Bob, a developer who was tasked with creating a text field component. Bob created the component and exposed all the required props like value, onChange, etc.

<TextField
  value={currentValue}
  onChange={onTextChange}
  ...
/>

But as new requirements came in, Bob had to keep adding more and more validation features to the component. Eventually, it became unmanageable.

<TextField
  value={currentValue}
  onChange={onTextChange}
  minLength={3}
  maxLength={20}
  ...
/>

At this point, Bob learned about the Inversion of Control pattern. The idea behind this pattern is to let the consumer of the component decide what they want to do with it, instead of the developer trying to anticipate all possible use cases.

Bob then refactored his component to use IoC:

<TextField
  value={currentValue}
  onChange={onTextChange}
  onValidation={(value)=> {
    if (value.length <= 3) return false;
    if (value.length > 20) return false;
    return true;
  }}
  ...
/>

Now, the consumer has extensive control over the validation, and can do anything they want with it, including making an external API call or comparing it with another value on the component.

But what about existing consumers who may not want this level of control or find it too complicated? That's where a wrapper component comes in handy.

// DEFINE
const TextFieldWithLength = ({minLength, maxLength, currentValue, onTextChange}) => {
	return <TextField
            value={currentValue}
            onChange={onTextChange}
            onValidation={(value)=> {
              if (value.length <= minLength) return false;
              if (value.length >= maxLength) return false;
              return true;
            }}
            ...
          />
}

// CONSUME
<TextFieldWithLength
  value={currentValue}
  onChange={onTextChange}
  maxLength={maxLength}
  minLength={minLength}
  ...
/>

By creating a wrapper component, consumers can have a simplified version of the original component that meets their specific needs.

In conclusion, the Inversion of Control pattern can help you create more flexible and reusable components. By letting the consumer decide what they want to do with your component, you can create a more modular and maintainable codebase.