DEV Community

Cover image for React Architectural Design: A focus on cohesion and coupling.
Somtochukwu Okafor
Somtochukwu Okafor

Posted on

React Architectural Design: A focus on cohesion and coupling.

One of the major design principles of Software Architectural design is to

increase cohesion and reduce coupling anywhere possible.

You might've stumbled upon this rule somewhere but in this article, I hope to explain it in the context of developing a react application.

Let us start by first defining what cohesion and coupling both mean in an engineering context.

Image of several modular connections
Photo by GuerrillaBuzz on Unsplash

Cohesion

It is defined as the degree of relationship between elements of the same module. It focuses on how a single class is designed. The higher the cohesiveness of the class, the better the design. Some of the benefits of higher cohesion is that highly cohesive classes are much easier to maintain and less frequently changed.

Coupling

It is defined as the degree of interdependence between the modules. It is about how much one module depends on or interacts with other modules.

Okay, now that we have a basic understanding of what cohesion and coupling both mean, let's move on to their effects and how best to approach reducing coupling and increasing cohesion to get a truly efficient react application.

1. Structure is key.

It is critical to programming to always create a good structure for your files in order to write higher level readable codes. When we talk about software architecture, in this context we are basically talking about its structure. When designing the structure of your react application it is preferable to either group modules by features/routes or by file type. You can also break down modules and group them in folders based on their category or function.

 src
├── components
│   ├── navbar.js
│   ├── component.js
│   └── footer.js
├── utils
│   ├── test-utils
│   │   ├── test-utils.js
│   │   └── index.js
│   |       
├── layout
│   ├── about.js
│   ├── contact.js
│   |
├── index.js
Enter fullscreen mode Exit fullscreen mode

In the example above you can see that we're grouping our modules by file type. Take note of how we arrange the folder and file structure.

2. Make your modules self-reliant.

You do not want your modules to heavily rely on each other, it is counter-intuitive and defeats the purpose we are trying to achieve. If elements are loosely coupled, changing one element does not affect or stir on changes in other elements hence driving up the levels of cohesiveness.

(Bad example)

import Button from "../button";

const App = () => {
  return (
    <div>
     <form action="">
       <input type="text" />
       <Button />
     </form>     
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Any changes made to the button component will affect all modules that make use of it.

(Good example)

const App = () => {
  return (
    <div>
     <form action="">
       <input type="text" />
       <button type="submit">Get Started<button/>
     </form>     
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the example above, the button can be changed and styled as you please without causing ripples in the fabric of your application.

3. Be careful when passing down data between components.

We already know by now that in a highly cohesive application, a change in one module will not affect other modules adjacent to it but we need to make sure of this by being careful when passing down data.

Header.js (good)

const Header = () => {
  return (
    <div className=''>
        <div className=''>
          <Link to="" className='self-center'>
            <p className=''>{"Title"}</p>
          </Link>
        </div>
        <hr className=''/>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

In the code block above we have a component that is static and has no outside data constraints to it at all. This is always good as you can change contents of p tag as much as you want without causing a chain of changes.

(good and bad)

const Header = (props) => {
  return (
    <div className='font-poppins mb-12'>
        <div className=''>
          <Link to="" className='self-center'>
            <p className=''>{props.title}</p>
          </Link>
        </div>
        <hr className=''/>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

In the example above you can see that we are passing a prop into our Header component and this component will be reused (coupled) through our application. This is a good practice because it makes the component more reusable. You can make changes to the title of the header without having to manually alter the Header.js file. But on the negative side if there are too many props in one component, a simple change will have a widespread effect that may not be easily manageable.

4. Elements in a module must have distinct and coordinated roles.

What roles do your intended function/component play in a page. Is it needed, will changing it affect another component in the module, have you structured them semantically to fit the flow of the page, is it independent, and with a single, well-defined purpose? These are some questions you must ask yourself during your developmental process.

Looking at the example below we can see that
roles is an external json file which has been imported containing hard-coded information about user roles.

Any changes made to the values inside roles.json will have no breaking effect on your file.

Dropmenu.js

import React from "react";
import { roles } from "../data";

const Dropmenu = () => {
  return (
    <>
      {roles.map((role, key) => (
        <div key={key} >
            {role.title}
        </key>
      ))}
    </>
  );
};

export default Dropmenu;
Enter fullscreen mode Exit fullscreen mode

NB: Using CSS frameworks

You need to be conscious in a situation you're using a CSS framework like Tailwind where your styles will advertently have to be inline. Tailwind follows a cohesive approach because most, if not all, your style pertaining to a particular module will be written in that module so changing its styles will not have a widespread consequence on the application.
Take for an example the code block below;

const Header = (props) => {
  return (
    <div className='font-poppins mb-12'>
        <div className='text-lg lg:text-2xl my-4'>
          <Link to="" className='self-center flex'>
            <p className='text-[#4E4A5A] text-[28px] leading-9 
             px-20'>{props.title}</p>
          </Link>
        </div>
        <hr className='h-0.5 border-none bg-neutral-200 
         shadow-sm'/>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

You can see the styles have been written inline, if this component is reused in your application, no further changes to the CSS will be needed because it hardcoded.

Outro

So far, we have gone over some of the steps to effectively reduce cohesion and increase coupling. It is worth noting that they are not independent terms and as so they cannot exist without each other. A system can't be 100% totally cohesive because there will always be a degree of coupling at play.
It is worthy to also note the context in which the application is being built; the levels of cohesion might depend on the level of the project or it's requirements.

I hope by now you've understood a little bit better how to write highly scalable code. Thank you for reading and please if you have any questions, feedback or more useful information to add, please leave a comment down below.

Top comments (7)

Collapse
 
brense profile image
Rense Bakker

I dont think #2 is quite true... A change in Button won't result in any changes in App, unless you change the props of Button. Moreover if you keep all your react code in one component, to make that component self-reliant, that single component is going to become huge and it will violate all the SOLID principles. What you want to do is the exact opposite. Put your component logic into separate smaller reusable components, each with their own (single) responsibilities. There's an article in the new React docs about this as well: react.dev/learn/thinking-in-react#...

Collapse
 
somtookaforr profile image
Somtochukwu Okafor

Thank you for the insightful comment @brense . The key point I noted from your comment was to put component logic in smaller *reusable * components. In order for the component to be fully reusable it would also be highly cohesive, don't you agree. And like I stated in my Outro: It is worthy to also note the context in which the application is being built; the levels of cohesion might depend on the level of the project or it's requirements. So you wouldn't say its outrightly not true. I'll be sure to check out the attached article, thanks again.

Collapse
 
barbra_okafor profile image
barbra okafor

Thank you for this insightful piece. 🙏🏽

Collapse
 
somtookaforr profile image
Somtochukwu Okafor

I'm glad you liked it.

Collapse
 
user_c67cac78b3 profile image
user_c67cac78b3

This was a very helpful read.

Collapse
 
somtookaforr profile image
Somtochukwu Okafor

I'm glad you found it helpful.

Collapse
 
0luchi profile image
0luchi

Nicee