Modularising React apps

Introduction:

React being a library doesn't provide a concrete structure to handle and modularise your code as done by other frameworks. Even though this gives flexibility to the developers to handle and modularise their code as per requirement, this becomes a nightmare especially when:

  • The application grows in size

  • Many teams working on different parts of UI and following different structure

  • Codebase increases

  • Overlap between different team's deliveries

Hence maintaining a structure and modularising your code becomes very important

The Problem:

export const EKart = () => {
    const [items, setItems] = useState<Items>([]);
    useEffect(() => {
      const fetchItems= async () => {
        const url = "https://kart.com/api/items";
        const response = await fetch(url);
        const items: Items[] = await response.json();
        setItems(items);
      };
      fetchItems();
    }, []);

    return (
      <div>
        <h3>Shop Items</h3>
        <div>
           { items.map((item) => {
               {item.count === 0 ? null : 
                <div key={item.id}>
                    <h3>{item.name}</h3>
                    <div> {item.count} </div>
                    <div> {item.currency === "dollar" ? "$" : "Rs" } </div>
                </div>
            }
           }
        </div>
   </div>
    );
  };

The above code even though is perfectly valid in react has some issues . It's not modularised with no separation of concern

This single component handles:

  • Plain view

  • Business logic

  • API and response handling

  • State handling

  • Conditional rendering

It's hard to read and understand the above code. Testing becomes extremely difficult as the business logic is coupled within the view. When such code grow in size throughout the codebase, maintenance becomes a nightmare.

What if I say that the modularisation can do this magic to the above code 😯

Let's modularise:

1) APIs:

Extract out your APIs to a separate file and return a promise resolved data.

const getItems = async () => {
        const url = "https://kart.com/api/items";
        const response = await fetch(url);
        const items: Items[] = await response.json();
        return items;
 };

2) Custom hooks:

You can design a custom hook to handle and call your API based on certain conditions. The retrieved result can be set, handled and managed using states.

const useItems = () => {
    const [items, setItems] = useState<Items>([]);

    useEffect(() => {
      const fetchItems= async () => {
        const itemsResp = await getItems()
        setItems(itemsResp);
      };
      fetchItems();
    }, []);

    return { items };
}

3) Presenters:

Presenters would contain all the pure business logic in javascript/typescript.

const isCountZero= (item: Item) => {
    return item.count === 0;
}

const getCurrency = (item: Item) => {
    return item.currency === "dollar" ? "$" : "Rs";
}

4) Types:

This file would include all the typescript types used in your code flow.

type Item = {
    id: number;
    name: string;
    count: integer;
    currency: string;
};

type Items = Item[];

5) Presentation:

The presentation would contain all the pure view related logic that can be rendered in the screen at the end. This would basically have your react component (.jsx, .tsx) files.

export const DisplayItems = (items) => {

    <div>
       { items.map((item) => (
            <DisplayItem 
               name = {item.name} 
               count = {item.count} 
               currency = {getCurrency(item)} 
               key={item.id}
            /> 
       }
   </div>
}

export const DisplayItem = ({ name, count, currency, key }) => {
    if(isCountZero(count)) return null;

    return (
      <div key={key}>
          <h3>{name}</h3>
          <div> {count} </div>
          <div> {currency} </div>
      </div>
    )
}

Finally you can import the hooks and components in the base component to render the details in the view

export const EKart = () => {
    const { items} = useItems();
    return (
      <div>
         <DisplayItems items = {items} />
      </div>
    );
};

Now as you can see we have boiled down our complex single component into a leaner simple component with modularisation. Moreover, each and every extracted item is unit testable and following separation of concern.

Benefits of Modularisation:

  1. Enhanced Maintainability

  2. Code Reusability

  3. Readability

  4. Improved scalability

  5. Ease in tech stack migration

  6. Testability

  7. Loose coupling of business logic and views

Do follow me on LinkedIn for more such blogs and technical contents. Thank you ☺️

References:

https://martinfowler.com/articles/modularizing-react-apps.html#IntroductionOfThePaymentFeature
https://refactoring.guru/design-patterns/typescript
https://www.patterns.dev/react/