Reusable Share Button using SOLID Principles + React + TypeScript

Reusable Share Button using SOLID Principles + React + TypeScript

The underworld of reusable components

Develop using React is fine. Split the components correctly are fine. Make it reusable, easy to read and easy to maintain could be a big challenge.

You probably faced many components split strangely, making you waste much time doing a simple bug fix. And that makes you think about the best way to organise your code and to separate your components. For this reason, when you start thinking about saving time and money on your project, you automatically begin thinking about reusable code, performance improvement, and easy-to-maintain code, abstractions and independent functions.

Sometimes, it is too late to think about it, and you need to refactor the entire feature that will cost you a lot of time. So, why not do it right from the beginning?


THE SOLUTION

A popular way to develop a clean code that achieves your goals and makes you proud of your project/code by using SOLID Principles.

Usually, the back-end uses it with classes. However, in React projects, the frontend developers have more responsibilities and are the guys responsible for developing a smart code. But the frontend guys are not always aware of SOLID and don't know how to apply it to React as it primarily uses functions and custom hooks instead of classes.

Splitting components is simple, and anyone can do it, but do it in the best way to not messy the code and be easy to read is a bigger challenge. So, we bring an exciting example of how to use SOLID with React.

At first, let's do a quick review on SOLID.

P.S.: The goal of this article does not to teach you SOLID and neither React. It only shows you an excellent example of how to use it together.


SOLID PRINCIPLES: QUICK OVERVIEW

  • S - Single-responsibility: A module should have only one reason to change, meaning that a module should have only one task. Meaning: Split your module to have unique functionalities. Manage the core functions, the actions and the types separated.
  • O - Open-closed: Modules should be open for extension but closed for modification. Meaning: Don't change the core function, make it extendable.
  • L - Liskov Substitution: Components and hooks in your module/project should be replaceable with instances of their subtypes without changing the module's correctness. Meaning: Create subclasses (different types of elements) that you can replace without breaking the code.
  • I - Interface Segregation: Do not force the user to implement features that they don't use, and do not force the user to depend on methods they are not using. Meaning: The developer using your module could choose what type and action to use with no pre-conditions.
  • D - Dependency Inversion: High-level modules with complex logic should be easy to reuse and can't be affected by changes in low-level modules that are providing utility features. Also, you must introduce an abstraction to decouple the high-level and low-level modules. Meaning: Decouple your module.


HOW TO USE SOLID WITH REACT FUNCTIONAL?

It's easier to show you an example, and we will do it, but first, I need to provide you with a list of thought about how I will do it.

  • Create an independent module that could be reusable in any other project.
  • Instead of classes/abstract/extend use components/custom-hooks/props


THE EXAMPLE: SHARE BUTTON

Brief: A share button module that I can add to any page to enable the user to share the current page's link or print it. We only have Facebook and Twitter, but at any time, the client would like to add new social media or even an option to share on WhatsApp or send by email. Also, it should be easy to create new buttons inside the module and set which button I would like to enable on each page.

React Share Button Template
React Share Button Hover Template


SHARE BUTTON WORKFLOW

Share button workflow
<ShareButton allow={['twitter', 'facebook', 'whatsapp', 'print']} />

To use the button, import the "ShareButton" component on your page and set props:

  • allow*: Array of button types.
  • className: The class for <buttons>.

* required param.


On the root file, as you can see on the workflow image, we have four files and two folders:

  • ShareButton.tsx: The main component that loops in the "props.allow" array to add desired buttons. Here, we also call the click handler custom hook. It's closed for modifications.
// imports

export interface ShareButtonProps {
  allow: ShareButtonsType[];
  className?: string;
}


export const ShareButton: React.FC<ShareButtonProps> = ({allow, className}) => {
  const {handleShareButtonClick} = useShareButtonActions();
  console.log(className);


  return (
    <div>
      <span>{shareLabel}</span>
      {allow.map((currentButton: ShareButtonsType, index: number) => {
        const ShareButtonComponent = buttonsConfig[currentButton].Component;
        return (
          <ShareButtonComponent
            onClick?={() =>
              handleShareButtonClick(currentButton, buttonsConfig[currentButton].action)
            }
            className={className}
            key={index}
          />
        );
      })}
    </div>
  );
};
  • share-button-actions.hook.ts: Handle the onClick dynamically. It's closed for modifications and open to extension.
// imports


interface UseShareButtonActions {
  handleShareButtonClick: (
    shareType: ShareButtonsType,
    actionType: ActionsType
  ) => void;
}


export const useShareButtonActions = (): UseShareButtonActions => {
  const { goToLink } = useOpenLinkAction();
  const { printIt } = usePrintAction();


  const handleShareButtonClick = (
    shareType: ShareButtonsType,
    actionType: ActionsType
  ): void => {
    const Handler = {
      link: goToLink,
      print: printIt,
    };


    Handler[actionType](shareType);
  };

  return { handleShareButtonClick };

};
  • share-button.config.ts: Editable file to config the buttons.
import {FacebookButton} from './buttons/FacebookButton';
import {PintrestButton} from './buttons/PintrestButton';
import {PrintButton} from './buttons/PrintButton';
import {TwitterButton} from './buttons/TwitterButton';
import {WhatsAppButton} from './buttons/WhatsAppButton';
import {ButtonsConfig} from './share-button.model';


export type ShareButtonsType = 'twitter' | 'facebook' | 'whatsapp' | 'print' | 'pintrest';
export type ActionsType = 'print' | 'link';


export const shareLabel = 'Share with your friends: ';


export const buttonsConfig: ButtonsConfig = {
  twitter: {
    action: 'link',
    url: 'https://twitter.com/share?url=',
    Component: TwitterButton,
  },
  facebook: {
    action: 'link',
    url: 'https://www.facebook.com/share.php?u=',
    Component: FacebookButton,
  },
  pintrest: {
    action: 'link',
    url: 'https://pinterest.com/pin/create/link/?url=',
    Component: PintrestButton,
  },
  whatsapp: {
    action: 'link',
    url: 'https://api.whatsapp.com/send?text=',
    Component: WhatsAppButton,
  },
  print: {
    action: 'print',
    url: null,
    Component: PrintButton,
  },
};

P.S.: I don't know when you are reading this article. Maybe you need to update the social network links.

  • share-button.model.ts: Models for the module.
import { ActionsType } from "./share-button.config";


export interface ShareButtonChildProps {
  onClick: () => void;
  className?: string;
}


export interface Actions {
  action: ActionsType;
  url: string | null;
  Component: React.FC<ShareButtonChildProps>;
}

export interface ButtonsConfig {
  [key: string]: Actions;

}
  • buttons/*: The available types of share buttons. Have one component for each type, and you can create how many as you need. See an example of FacebookButton.
// imports


export const FacebookButton: React.FC<ShareButtonChildProps> = ({onClick, className}) => {
  return (
    <button onClick?={onClick} className={className}>
      <i className="fab fa-facebook-f"></i>
    </button>
 );
};
  • actions/*: The available actions for share buttons. Have one custom hook for each action, and you can create how many as you need.
// imports

interface UseOpenLinkAction {
  goToLink: (buttonType: ShareButtonsType) => void
}


export const useOpenLinkAction = (): UseOpenLinkAction => {
  const goToLink = (buttonType: ShareButtonsType): void => {
    const currentUrl = window.location.href;
    const link: string = buttonsConfig[buttonType].url + currentUrl;
    window.open(link);
  };


  return { goToLink };
};


SHARE BUTTON: CREATE NEW TYPE

  1. Create a new button file inside the "/buttons" folder. Respect the name convention and use other buttons as a reference.
  2. Go to "share-button.config.tsx" to add your new button.
  3. Add it on "buttonsConfig".
  4. Use the same key name to add it on "ShareButtonsType".


SHARE BUTTON: CREATE NEW ACTION

  1. Create a new action file inside the "/actions" folder. Respect the name convention and use other actions as a reference.
  2. Add your new action (custom hook) you've created to the Handler at "share-button-actions.hook.ts".
  3. Go to "share-button.config.tsx" to add your new action.
  4. Set the buttons that will use it on "buttonsConfig".
  5. Use the exact value of action at "buttonsConfig" to add it to ActionsType.


WHERE ARE THE SOLID PRINCIPLES IS THIS EXAMPLE?

  • Single-responsibility: The share types and actions are independent, and each one has a single purpose, share on Facebook, Twitter or Print.
  • Open-Close: You don't need to modify the core functions. You only need to extend it to add new actions. You can also add config new types without modifying the core function because we are using and config file.
  • Liskov Substitution: We created subtypes, types and actions, and stored them separated from the core files, and we allow you to use any type with any action without affecting the correctness of the app.
  • Interface Segregation: You are enabled to use only the share buttons you want. And you can easily do it when you call the ShareButtons component while defining the "props.allow".
  • Dependency Inversion: We decouple the whole module. Each action is abstracted and not affecting the core functions.


Share Button Source Code: github/rafaelperozin/react-smart-share-button


So, young grasshopper ??...

Miyagi - Young Grasshoppe

In this article, we talked about SOLID, and we also saw a simple and complete example of how to use it in React with functional components. Now, I suggest you refactor some features you already develop to practice and better understand how to do it.

I don't use it everywhere, but I usually use part of it to keep my files organised to abstract some functions and develop a clean code correctly.

So, go there and share this article with your teammates to build together reusable, extendable, easy to maintain and easy to understand components.

See you in the next article! ??

要查看或添加评论,请登录

Rafael Perozin的更多文章

社区洞察

其他会员也浏览了