This article is about a problem many of us encounter in React & Frontend development (sometimes even without realizing that it's a problem): Ha...
For further actions, you may consider blocking this person and/or reporting abuse
I would like to propose a different solution to this issue, which uses composition with React components (and a bit of restructuring of the original code) to create specialized UI per locale.
(used Google Translate so translations may be poor, sorry!)
Please refer to
./src/feature/pricing/JapanPricing.tsx
to see how clean the solution is using component composition.I agree that having business logic mixed in with the components (as in the original example) is problematic and should be extracted from components to make it easier to test. However, in your proposed solution, the logic is still somewhat distributed among several components and it requires passing down the strategy multiple components. I'm not a big fan of using these type of abstractions as it is bad for reusability of components (I want to apply
<PricingCard>
in a different context, but I always need to pass astrategy
prop).One hiatus in your examples is that you do not cover translation of text. Translation is a concern that should be handled separately from your business logic and presentation.
In a real life React app you would handle this using something like react-i18next. I implemented a very rudimentary version of a translation framework that supports translation bundles and string interpolation. This means you can use the
<T>
component to translate a key for you and also pass in variables:The same thing is done for formatting currency in the user's locale: the
useLocale()
hook exposes theformatCurrency()
function that formats an number based on the user's locale. I think this is the correct way to separate "localisation" concerns from "presentation" / "business logic" concerns.The issue that remains is creating a specialized UI that shows an additional message about discounts for a specific locale. This is where React shines with component composition, in my opinion. I make a distinction between "UI components", that take simple input props and render them to the screen, and "container components" that compose a more complex UI and apply some conditional rendering . I make use of the
children
prop to make component composition easier.In my final solution, I created
JapanPricing.tsx
where I compose the UI with the<PricingCard>
and<DiscountMessage>
components. This way I don't need any additional logic to determine whether a discount should be applied (strategy.shouldDiscount()
). I just know I need it here so I render theDiscountMessage
component!You could move the
<T>
component inside the<DiscountMessage>
component, among other things. The way you design your component API depends on your needs, how reusable the component needs to be. I mainly wanted to showcase how useful composition can be in this example!The logic for calculating the discount price is in a separate module, yet still colocated with the other modules related to pricing. There is a little bit of code duplication for composing the UI, but this is code that is simple to read and to throw away when it's no longer needed. Making a change is simple as we separate UI concerns from business logic.
Hey Edwin,
thanks for your elaborate answer and the elegant solution. In my opinion it is way easier to use and understand. Also it seems to prepare better for future feature changes or individual adaptions (translations, different UI etc.), things that happen in web dev all the time. And lastly, it feels way more like React and Javascript is intended to use.
In fact, I wouldn't event advise using the solution from the article. I'm surprised that I'm not reading more concerns here.
I recently found another article about this exact same topic and they make a valid argument for the Strategy pattern that is not mentioned in Hugo's article:
So it makes sense to apply the Strategy pattern if you want to extract the business logic so you can easily test it in isolation or apply it in a completely different context (NodeJS backend, Vue, etc). However, it still does not feel very React-ish and it adds some complexity (having to pass in the Strategy object everywhere). These are the trade-offs you need to consider.
Hi, thanks for the comments. I need to clarify that this is a contrived and straightforward example that I came up with to demonstrate the usage of the Strategy Pattern. Any sane developers would use i18n to solve the original problem, but that's not the point.
I'll put a heads-up in the article so that people are not misled.
This is a great idea. Curious to know your thoughts on how do we reduce code duplication for the UI composition.
I'm not sure what you mean - duplication in this example or in general?
The best thing you can do is to identify patterns in your app and extract those patterns into new compnents. For example, if you are building a list of cards and every card has an image, header and title. Then later you need that same card in some other place, then you can extract it its own
Card
component.As with regular functions, you need to think about the API design of the
Card
. What props can it take, what use cases should it be able to handle? I like to make a distinction between "presentational components" (that only display data that is given to them so that you can keep them simple) and "data fetching components" (the ones that fetch data from the API and pass it to presentational components).To practise this, you can take a design (from Figma or whatever) and draw boxes around parts of the UI that you think should be a component. The React docs explain how to do this.
Scrolling to find something good these day is hard, but I have to log in and save this article because it's worth spending on. Ty Hugo!!
Yes, but how do I organize this code now? I mean the folder structure, where should I place these new classes?
I'm gonna have another article talking about folder structures soon, which will fully resolve your question. Please look forward to it.
Thank you in advance.
@radandevist
Sorry for the very late response. I kinda forgot to write the article, but here it is at last: dev.to/itswillt/folder-structures-...
Great writing! 🎉 Looking forward for more.
Wow, didn't knew about this
Really nice article. Felt glad someone was doing the good work of reviving old design patterns and applying them into react.
Thank you Hugo, amazing post
Great article. Thanks for sharing 🙏
Love it. Need more quality content like this!
Really nice. I was hoping to implement something like this for a long time but had no idea. Although, my usecases are different but this article will surely help. 😊 Thanks.
Thank you Hugo, great post!
I have one question. Why is the strategy class made to not accept a price?
It depends on how you define a "strategy". In this example, the scope of a strategy is about how the price should be discounted and what the discount message is, etc. Therefore, it doesn't need to know the exact price itself.
You can define the price inside the strategy class. However, you'd have to reinstantiate the class every time you want to change the price. That'd be a little bit painful I think.
Thank you. That makes sense.
Thanks Hugo for this great work!
Nice article!
Awesome Huy
That finds good....keep writing
Great article!. I hope read more your article like this :D
so effective! Great read, Hugo!
Being your fan from now :O
Great Article, Following you for more like these🥇
Great read and can be used with hooks as well as opposed to having class components with extend the abstract call
Thanks for your feedback! Great to hear that it was a good read.
Actually, I'm not using a class component here. Also, this can be implemented using hooks, but the logic is quite pure and not coupled to React at all, so I don't want force the logic into a hook.
Moreover - the strategy pattern makes it really easy to test as unit test.
Hey, @itswillt
It is not a good idea to use the abstract class as a type of injectable stratagy. It is better to define the interface. More than, this interface must be defined on the statagy application side, not on the implementation side (see DIP).
Another problem I see is that we are trying to use a design pattern for issue that must be solved in another way.
If we are talking about different cuntries and currencies, it seems we need to talk about a different languages.
So, it means all story related to the amount formatting, discount messages, and even static text inside of the
PricingCard
and its sub-components are matter of localezation.So, it is better not to invent a weel, but use old, kind
i18
.That means that the final interface of the
PricingCard
must looks like that:And all the rest doing with
i18next-react
(or something similar).We had text generation logic out of the view, and we got a problem when we met a need to support several languages -- so, it is better to keep all text as much close to be replaced with
{t('MESSAGE')}
as it is possible from the project beginning."Strategy" in JS? Ya mad. The pattern exists to bypass the composition limitations of OOP languages, it's not useful in JS
I would not do any calculation in React. All should be comming from the backend service. If you ditch React, you are ditching business logic. This is a violation of the thin client definition.
By no means in the article am I suggesting that you should put most of your domain business logic in the client side. This is actually a contrived example of how the Shotgun Surgery can happen within a codebase and how the Strategy Pattern helps to solve it. In a frontend application, you will have a lot of application logic regardless, so the Shotgun Surgery is bound to happen if you're not mindful.
Why not creating a config file let's say a JSON file that contains all informations about each country instead of creating components.
Hiện tại anh đang làm việc ở công ty nào vậy ạ ?
Dope & helpful article. We more articles that cover design patterns in a react approach.
Can't wait for the other topic of Hugo
Love it, love you.