Code Reusability in React: 3 Effective Strategies

Code Reusability in React: 3 Effective Strategies

React is inherently founded on the principle of reusability. This foundational concept is pivotal for enhancing code maintenance and facilitating a modular development paradigm. React's component-based architecture allows developers to create encapsulated and reusable building blocks, enabling the construction of complex UIs with ease.

In this article, we explore optimising code reusability in React by unveiling three effective strategies. By understanding and harnessing React's innate capacity for component reuse, developers can significantly enhance the maintainability and scalability of their applications.

Let us dissect three potential problems you might face and the practical approaches to streamline your React development workflow when faced with them. I'll use code snippets from a project my team and I are working on.

Problem 1: Two components differ in logic but share almost identical JSX.

In the project we are working on, we have these cards on the UI:

The initial screenshot is from the Payments page, monitoring tenant payment patterns. The second one is from the Dashboard page, displaying summaries of tenant occupancy, complaints, and requests. Yet, as evident, they share identical structures, capturing the card name, a numerical value, and the change in that number compared to the previous month.

Solution: Create a reusable component containing only JSX.

The solution to the identified problem is creating a reusable component containing only JSX. In our project, this is the general (redacted) structure of that component:

const ReusableCard = ({ cardName, count, comparison }) => {
    return (
        <VStack>
            <HStack justify={'space-between'} w={'100%'}>
                <Text>
                    Total {cardName}
                </Text>
                <Flex>
                    <Icon
                        as={ cardIconsMap[cardName] || null }
                    />
                </Flex>
            </HStack>
            <Heading as={'h2'}>
                {count}
            </Heading>

            <HStack justify={'space-between'}>
                <Image
                    src={
                        comparison < 0
                            ? reusableCardIcons.decreaseIcon
                            : comparison > 1
                            ? reusableCardIcons.increaseIcon
                            : null
                    }
                />

                <Text>
                    {!isNaN(comparison) && comparison !== 0
                        ? `${comparison}% vs last month`
                        : !isNaN(comparison) && comparison === 0
                        ? 'No change vs last month'
                        : null}
                </Text>
            </HStack>
        </VStack>
    );
};

Through the encapsulation of the common structure within the ReusableCard component, we not only simplify code management but also boost maintainability. This versatile component takes three common values as props, utilizing data from different API endpoints to ensure a uniform output on both pages.

By adopting this methodology, we not only eradicate repetitive code but also advocate for a modular and efficient coding practice. The ReusableCard component becomes a central point for rendering shared elements, contributing to a streamlined and cohesive development process.

Problem 2: Two components have similar logic but different JSX.

A common encounter with this challenge is often in navigation menus, where the operational logic often remains consistent while there is a divergence in presentation between mobile interfaces (housed within a Drawer or Dropdown component) and desktop interfaces (featured in a Sidenav or Topnav).

This is an illustration of this scenario in our project:

Balancing the need for uniform functionality and adapting to the specific JSX requirements of mobile and desktop interfaces prompts the exploration of effective solutions.

Solution: Implement a reusable hook containing the common logic, and employ it within both components.

To tackle this challenge, and similar ones, we implement a reusable hook that consolidates the common logic, and then call it within both components.

import { useQuery } from '@tanstack/react-query';
import { api } from '../../../api/axiosConfig';

const fetchMenus = async () => {
    try {
        const { data, status } = await api.get('/menus');
        if (status === 200) {
            return data;
        } else {
            throw new Error(`Failed to fetch menus.`);
        }
    } catch (error) {
        throw new Error(
            `Failed to fetch menus. Check your network connection and try again.`
        );
    }
};

export const useMenusQuery = () => {
    return useQuery({
        queryKey: ['menus'],
        queryFn: () => fetchMenus(),
        keepPreviousData: true,
    });
};

By implementing this solution, we establish a robust foundation for managing shared logic effectively, promoting code consistency, and ensuring maintainability across interconnected components.

PS: Hooks may be a bit of an overkill for your circumstance and more often than not you only need to define the common logic in an exported function/variable and import it in the two components.

Problem 3: Two components demonstrate similar logic and share common JSX structures.

There are cases where we find ourselves in situations where two components not only demonstrate similar logic but also share identical JSX structures, maybe bar very few styling differences between them.

We have these three buttons on our Landing Page:

They share a common operational logic, utilizing a Popover component to display additional menu options on hover. With similar structures, the only variations are in the "Get Started for Free," Call-to-Action lacking a caret icon, and the "Login" button sporting a transparent background.

Solution: Consolidate the components and introduce props to manage variations.

This is a redacted code of how we have reused the same logic and JSX for all three components:

const PopoverMenu = ({
  buttonName,
  buttonWidth,
  buttonHeight,
  borderColor,
  padding = '1rem',
  backgroundColor = '#fff',
  justify,
  textColor = '#fff',
  dropdownIcon = true,
}) => {

  return (
    <Popover trigger={'hover'} placement={'bottom-start'}>
      <PopoverTrigger>
        <Box
          as='a'
          cursor={'pointer'}
        >
          <HStack
            bg={backgroundColor}
            border={`1px solid ${textColor}`}
            borderColor={borderColor}
            width={buttonWidth}
            height={buttonHeight}
            borderRadius={'1rem'}
            p={padding}
            justify={justify}
          >
            <Text color={textColor}> {buttonName}</Text>{' '}
            {dropdownIcon && <ChevronDownIcon color={textColor} />}
          </HStack>
        </Box>
      </PopoverTrigger>
    </Popover>
  );
};

export default PopoverMenu;

We utilize the backgroundColor prop to assign a distinct colour to the Login button compared to the others. Additionally, the dropdownIcon prop, a boolean, serves to identify which buttons will feature a caret. By default, it is set to true, indicating that the absence of a caret on our Popover components is more of an exception than the norm.

//Login Button
    <PopoverMenu
      buttonName={'Login'}
      buttonHeight={'2.75rem'}
      backgroundColor='transparent'
      options={filteredLoginOptions}
      isLoading={isLoading}
    />
//Get Started for Free Button:
      <PopoverMenu
        buttonName={'Get Started for Free'}
        buttonHeight={'2.75rem'}
        textColor='brandPrimary'
        options={filterRegisterOptions}
        buttonWidth={'20rem'}
        dropdownIcon={false}
        justify={'center'}
      />

This approach not only streamlines our codebase but also ensures adaptability to different requirements within each component while maintaining logical and structural consistency.

Conclusion

In this article, I've discussed three common challenges encountered in React component development and proposed effective solutions to optimize code structure and maintainability.

From redundancy in similar components, components with similar logic but different JSX structures and finally components sharing both similar logic and JSX structures, I hope each corresponding solution will equip you as a React developer with practical optimization strategies for code reusability and a modular development approach.

Happy Sunday!