The thin line between DRY and "Ambiguity" in your React Applications👀

The thin line between DRY and "Ambiguity" in your React Applications👀

I got myself a new laptop a couple of weeks ago, so I had to clone some of my work from Github again. I decided to clone one of my old repo. Your guess is the same as mine, I was shocked at some of the things I wrote years back. This feeling prompted this article.

There is a euphoric feeling you get as an engineer when you write clean and precise code. This also gives you more confidence to do better or even go back and refactor that rusty code you wrote a few years back 😅.

As a software developer, you have probably heard the phrase "Don't Repeat Yourself" (DRY) before, and if this is your first time hearing it, that is also great. This principle is often emphasized in software development. It is meant to encourage developers to write code that is concise, maintainable, and scalable, by avoiding duplication of code and abstracting common functionality into reusable components or utility functions. This aims to eliminate redundancy and duplication in your code, as this can lead to a number of problems, including increased complexity, decreased maintainability, and a higher likelihood of bugs.

However, in the pursuit of DRY code, it is important to be mindful of the thin line between DRY and ambiguity. Ambiguity refers to situations where it is unclear what a piece of code is supposed to do or how it is supposed to be used. Ambiguity can lead to confusion, and messy code and make it difficult for developers to understand and work with the code.

In this article, I will share some examples where React developers in their attempt to practice DRY, introduce ambiguity into their application thereby making it messy and buggy. I’ll also walk you through how you can strike a balance between DRY and ambiguity in your React applications.

Example 1: Ternary Operators

In cases where we want to use one component to handle different conditions. In other to avoid writing new components. A lot of developers try to cramp too much information into one component because they want to make the component reusable for different cases and practice their version of "DRY". This ends up making the code ambiguous, very messy, and difficult to maintain.

import React from "react"

const AppView =({ isVerified, isAdmin, isLoggedIn})=>{
return(
<div>
{isLoggedIn ? (isVerified && isAdmin ? 'Welcome back, admin!' : isVerified && !isAdmin ? 'Welcome back!' : 'Please verify your email') : 'Please log in'}
</div>
)
}

This example is common with React developers, especially newbies. In this example, the ternary operator is nested, which can make the code more difficult to read and understand. In the attempt to put everything in one component, and not repeat themselves, the developer has crossed over from DRY to messy and ambiguous in a matter of few lines. As more conditions are introduced later the code becomes messier and harder to read and maintain.
A way you can make the example above easier to understand is to use switch statements and render different messages shown based on the condition stipulated.

Example 2;

Suppose you have a component that displays a list of items, and you want to reuse this component in multiple places throughout your application. You might be tempted to create a very generic component that can handle any type of list, using props to specify the data and the rendering details.

import React from 'react';

const List =({data, renderItem})=>{
return(
<ul>
{data.map(item => (
        <li>{renderItem(item)}</li>
      ))}
</ul>
)
}

In this example, the List component is very generic, as it can be used to display any type of data by passing in a renderItem prop that specifies how each item should be rendered. While this approach might seem DRY, it can also be confusing to use, as the meaning of the renderItem prop is not immediately clear.
A way to remove ambiguity from this example is to add comments to the code. This adds clarity and meaning to what renderItem is.

Example 3: HOC (Higher order components)

Another way that ambiguity can be introduced is by overusing higher-order components. These patterns can be useful for adding generic behavior to a component, but if they are used excessively, they can make the component hierarchy more difficult to understand and debug.

import React, {useState} from 'react';

const withPagination=(WrappedComponent)=> {
   const [page, setPage] = useState(1)

   const handleNextPage = () => {
      setPage(prevPage => (prevPage + 1));
    };

   const handlePrevPage = () => {
      setPage(prevPage => (prevPage - 1));
    };

   return(
        <WrappedComponent
          {...props}
          page={page}
          onNextPage={handleNextPage}
          onPrevPage={handlePrevPage}
        />
      );
    }

In this example, the withPagination the higher-order component adds pagination functionality to a wrapped component by providing props for the current page, and functions for handling next and previous page actions. While this approach can be useful and DRY, it can also make it more difficult to understand the component hierarchy. This is because it is not immediately clear what the pagination-related props and functions do or where they come from.

So how can you strike a balance between DRY and ambiguity in a React codebase? Here are some tips:

  1. Use descriptive and clear names for variables and functions: By giving your variables and functions descriptive and clear names, you can make it easier for other developers to understand their purpose and how they fit into the overall codebase. This can help to avoid ambiguity and make the code easier to work with.

  2. Document your code: Adding comments and documentation to your code can help to clarify its purpose and how it is intended to be used. This can be especially important for code that is complex or not immediately intuitive. By providing clear documentation, you can help to avoid ambiguity and make it easier for other developers to understand and work with your code.

  3. Be selective about when to abstract. Think carefully about whether it is worth creating a generic component or higher-order component, or whether it would be clearer to create a specific component for a particular use case.

Conclusion 👋

The importance of DRY code in an application can never be over-emphasized. While we strive to keep our code as "dry" as possible, it is also important not to introduce ambiguity into our code in the process of practicing DRY.

I'd also love to hear your thoughts on how you can lessen ambiguity in your applications while practicing DRY, so feel free to share in the comment session.

Stay DRY and don't get messy😉.