DEV Community

Cover image for Organizing your React code: Cohesion and Coupling
Ignacio Mattos for Cloud(x);

Posted on • Updated on

Organizing your React code: Cohesion and Coupling

Have you ever needed to spend a considerable amount of time reviewing your own code (written just a couple of months ago) or been assigned to a new project just to find a pile of messy and eternal files?

I'm sure we've all had the feeling that a fix that took a day could've been solved in a fraction of that time by only having the code a little more organized.

Consequences of spaghetti code are usually spending days instead of hours solving problems, code repetition, inconsistencies between behaviors that should work the same way, inaccurate estimations and, of course, some headaches.

In this series I’ll try to share some notions that help me when it comes to designing clean and maintainable components, architecture and logic.

Cohesion and coupling

These two are key concepts that will go along with you wherever you code, leaving aside the particular language or framework you work with. Since they've already been explained way better than I could do here and now, I'm only talking about the core concept.

Imagine that your web has a couple of pages that share a component. Let's also say this component has a basic behaviour: a button that requests some data from an API, formats it and stores it, but this process slightly varies in each case.

It's easy to see that we are going to reuse at least a part of our code, keeping in mind that our goal is to have as little code as possible (we'll return to this later). The point is, which is going to be our criteria here? Is there a standard to conform to when designing our reusable component? The golden rule is: the greatest cohesion, the lowest coupling.

What does this mean? Said in a few words, cohesion is related with the criteria that gathers the inside elements of your component, while coupling has to do with the external relations established between your component and the environment in which it is used (I mean, everything else). And let me just add that, as we have a components tree, these concepts are to be transported to wherever you want, from less to more abstract.

For more info about this topic have a read.

Why do we want our components to be cohesive and decoupled?

The stronger relation you achieve between your inside elements, the more definite and accurate the identity of your component is. The more abstracted you keep from the external requirements, the freer you get to reuse and adapt your components to different situations.

Should I declare every single behavior the button might have when handling the click? Seems we could get a handler from our props, decoupling our logic from the one depending on foreign elements. Are you sure it's clean to fill your component with ifs to validate what to render or not according to every possible use case? Let's have a simple one-job component as external logic is out of our scope since it is inside another component's one.

Have you heard about the single-responsibility principle?

Let me share a simple example so we can figure this out:

import React from "react";

const MyComponent = (props) => {
    const {isHeader = false, isFooter = false, text} = props;

    const headerTitle = isHeader ? "MyHeader" : "";
    const footerTitle = isFooter ? "MyFooter" : "";
    const styles = isHeader
        ? {
            backgroundColor: "red",
            fontWeight: "bold",
            width: "200px",
        }
        : {
            backgroundColor: "green",
            fontWeight: "lighter",
            width: "500px",
        };

    const handleClickHeader = () => console.log("Getting header's data from API.");
    const handleClickFooter = () => console.log("Getting footer's data from API.");

    return (
        <div style={styles}>
            {isHeader && <p>{headerTitle}</p>}
            <p>{text}</p>
            <button onClick={isHeader ? headerClickHandler : footerClickHandler}>
                Click Me!
            </button>
            {isFooter && <p>{footerTitle}</p>}
        </div>
    );
};

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

As we said, in these two scenarios the components' behaviour is similar. We can find differences in the button's callback, in the labels' content and in the subcomponents' order and styles, but the core idea of the component is the same for both cases.

Besides, it's bothering to see that the component is full of validations that adjust what the component renders and does according to the props' values. This means the component is coupled to the context's needs.

Doing it better 😃

As all of this can be parameterized without losing the component's identity, we are going to extract the contextual logic to achieve a simpler and more compact component that only does what it's meant to.

Following this same logic, we end up generating a few more components that gather their own specific logic (including a parent Container):

import React from "react";

const MyComponent = ({styles, text, onClick}) => (
    <div style={styles}>
        <p>{text}</p>
        <button onClick={onClick}>Click Me!</button>
    </div>
);

const Header = () => {
    const headerStyles = {
        backgroundColor: "green",
        fontWeight: "bold",
        width: "200px",
    };
    const headerTitle = "MyHeader";

    return (
        <div>
            <p>{headerTitle}</p>
            <MyComponent
                styles={headerStyles}
                text="Footer"
                onClick={() => {
                    console.log("Getting header's data from API.");
                }}
            />
        </div>
    );
};

const Footer = () => {
    const footerStyles = {
        backgroundColor: "green",
        fontWeight: "lighter",
        width: "500px",
    };
    const footerTitle = "MyFooter";

    return (
        <div>
            <MyComponent
                styles={footerStyles}
                text="Footer"
                handleClick={() => {
                    console.log("Getting footer's data from API.");
                }}
            />
            <p>{footerTitle}</p>
        </div>
    );
};

const Container = () => (
    <div>
        <Footer/>
        <Header/>
    </div>
);

export default Container;
Enter fullscreen mode Exit fullscreen mode

But hey! Didn't I say one of our targets is to have less code? 🤔 Why do we have a larger amount lines in the
second snippet?

Well the point here isn't only the code you use to define your components, but also the potential repetition you are avoiding by having a reusable example that can adapt to different situations and be upgraded if your requirements change.

In the next post, I am going to share a few tips that help me organize my files.

But if this didn't move you to review your programming habits, check this out: the person on the left is a responsible developer who follows the best practices and makes an effort to improve his code's legibility when he has the chance.
The one on the right is his twin brother, who thinks it’s fine to copy&paste the same logic over and over again and concentrate his code in a few files.
Choose your cards wisely!

people

Top comments (0)