⚛️ Applying Strategy Pattern in React (Part 1)
Tags: react, typescript, javascript, webdev
Published: 2022-12-14 07:56:23

This article is about a problem many of us encounter in React & Frontend development (sometimes even without realizing that it's a problem): Having a piece of logic implemented throughout different components, hooks, utils, etc.

Let's dive into the problem details and how to solve it. As the title suggests, we're going to use [the Strategy Pattern](https://refactoring.guru/design-patterns/strategy) to solve it.

## The problem: Shotgun Surgery

[Shotgun Surgery](https://refactoring.guru/smells/shotgun-surgery) is a code smell where _making any modifications requires making many small changes to many different places_.

![Shotgun Surgery Code Smell](https://refactoring.guru/images/refactoring/content/smells/shotgun-surgery-01-2x.png)
_(image source: https://refactoring.guru/smells/shotgun-surgery)_

How can this happen in a project? Let's imagine we need to implement pricing cards for a product, and we adjust the price, the currency, the discount strategy and the messages based on where the client is coming from:

![Pricing Cards](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5iz9oso7ozaqm0qibd76.png)

In this _contrived_ example, without the existence of localization, the pricing card might be implemented as follows:

- Components: `PricingCard`, `PricingHeader`, `PricingBody`.
- Utility functions: `getDiscountMessage` (in **utils/discount.ts**), `formatPriceByCurrency` (in **utils/price.ts**).
- The `PricingBody` component also calculates the final price.

Here's the full implementation:

{% codesandbox react-strategy-pattern-problem-h59r02 %}

Now let's imagine we need to change the pricing plan for a country, or add a new pricing plan for another country. What will you have to do with the above implementation? You'll have to at least modify 3 places and add more conditionals to the already messy `if-else` blocks:

- Modify the `PricingBody` component.
- Modify the `getDiscountMessage` function.
- Modify the `formatPriceByCurrency` function.

If you've already heard of S.O.L.I.D, we're already violating the first 2 principles: The Single Responsibility Principle & The Open-Closed Principle.

## The solution: Strategy Pattern

The Strategy Pattern is quite straightforward. We can simply understand that each of our pricing plans for the countries is a strategy. And in that strategy class, we implement all the related logic for that strategy.

Suppose you are familiar with OOP, we can have an abstract class (`PriceStrategy`) that implements the shared/common logic, and then a strategy with different logic will inherit that abstract class. The `PriceStrategy` abstract class looks like this:

```ts
import { Country, Currency } from '../../types';

abstract class PriceStrategy {
  protected country: Country = Country.AMERICA;
  protected currency: Currency = Currency.USD;
  protected discountRatio = 0;

  getCountry(): Country {
    return this.country;
  }

  formatPrice(price: number): string {
    return [this.currency, price.toLocaleString()].join('');
  }

  getDiscountAmount(price: number): number {
    return price * this.discountRatio;
  }

  getFinalPrice(price: number): number {
    return price - this.getDiscountAmount(price);
  }

  shouldDiscount(): boolean {
    return this.discountRatio > 0;
  }

  getDiscountMessage(price: number): string {
    const formattedDiscountAmount = this.formatPrice(
      this.getDiscountAmount(price)
    );

    return `It's lucky that you come from ${this.country}, because we're running a program that discounts the price by ${formattedDiscountAmount}.`;
  }
}

export default PriceStrategy;
```

And we simply pass the instantiated strategy as a prop to the `PricingCard` component:

```tsx
<PricingCard price={7669} strategy={new JapanPriceStrategy()} />
```

with the props of `PricingCard` defined as:

```ts
interface PricingCardProps {
  price: number;
  strategy: PriceStrategy;
}
```

Again, if you know OOP, not only we're using Inheritance, but we're also using Polymorphism here.

Here's the full implementation of the solution:

{% codesandbox react-strategy-pattern-solution-mm0cvm %}

And let us ask the same question again: How do we add a new pricing plan for a new country? With this solution, we simply need to add a new strategy class, and we don't need to modify any of the existing code. By doing so, we're satisfying S.O.L.I.D as well.

## Conclusion

So, by detecting a code smell - Shotgun Surgery - in our React codebase, we have applied a design pattern - Strategy Pattern - to solve it. Our code structure went from this:

![Before - Shotgun Surgery](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wn31uolfo6xfy3mh8fo.png)

to this:

![After - Strategy Pattern](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xheeb2splcwj2kkqwsfo.png)

Now our logic lives in one place and is no longer spread throughout many places anymore. Do note that this whole article revolves around a contrived example. Practically, the Strategy Pattern can be implemented in simpler ways (using objects instead of classes). Please check out part 2 of this series:

{% link https://dev.to/itswillt/applying-design-patterns-in-react-strategy-pattern-part-2-221i %}

---

If you're interested in design patterns & architectures and how they can be used to solve problems in the Frontend world, make sure to give me a like & a follow.

{% user itswillt %}