Let’s say we have this component:
const Card = ({ title, description, price, tag, category }) => {
return (
<div>
{title && <h5>{title}</h5>}
{description && <div>{description}</div>}
{price && <div>{price}</div>}
{tag && <div>{tag}</div>}
{category && <div>{category}</div>}
</div>
)
}
With Compound component pattern we can refactor this into:
const Card = ({children}) => <div>{children}</div>
const Title = ({children}) => <h5>{children}</h5>
const Description = ({children}) => <div>{children}</div>
const Price = ({children}) => <div>{children}</div>
const Tag = ({children}) => <div>{children}</div>
const Category = ({children}) => <div>{children}</div>
And usage:
<Card>
<Title>this is title</Title>
<Description>description</Description>
<Price>price</Price>
</Card>
We can easily omit what we don’t need, like Tag
and Category
in the above example. Now each separated component does only one thing and the parent became clean.
All Good. But doesn’t this violate the DRY principle? Now we have to copy everywhere the ordering logic of the elements.
title > description > price > tag > category > and more in future
If one day we decide to change the order of the elements, category
above tag
, price
above description
… it would require changing this everywhere that those components are used. If we don’t want the parent to be responsible for knowing each children it receives, who is responsible to keep the ordering same on each place.
This is simple example with simple layout. But the layout can be way more complex. Like tag
and category
could share same row (2 columns) or price
can be positioned top right. If we use the Compound pattern, who is responsible for keeping the layout same everywhere?
Oktay Yuzcan is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
2
If the intention of the <Card>
component is to have a consistent layout of elements, then it’s not a good fit for the composite pattern. That pattern is suited to cases where different users of the component want different layouts.
So to answer the direct question:
If we use the Compound pattern, who is responsible for keeping the layout same everywhere?
No-one. It is meant to be not the same everywhere.
You wrote about the second example:
We can easily omit what we don’t need, like Tag and Category in the above example.
When I interpret your first example correctly, that function has the same property – so I don’t see any benefits by the second one in regards to this point.
Now each separated component does only one thing and the parent became clean.
This is a pretty common misunderstanding of what “one thing” means. First, to me, your first example is clearly “clean enough” for my taste. Of course, your real component may be more complex and look more convoluted, but the straightforward approach here would be
-
to extract the inner logic of the Card function exactly the way it was to sub-functions for example
const Title = ({title}) => {title && <h5>{title}</h5>} const Description ...
-
and then call these subfunctions from the Card function like
const Card = ({ title, description, price, tag, category }) => { return ( <div> {Title(title)} {Description(description)} {Price(price)} {Tag(tag)} {Category(category)} </div> ) }
(forgive me if I did not get the syntax right, I am not a JS coder and definitely not a ReactJS coder, but I guess you get the idea).
Now, the Card
function still does “one thing”: it coordinates the calls to the other functions. If you like, you can combine the Card function together with its helper functions into a class.
So to sum this up:
-
yes, your original idea of using the Compound component pattern in the shown way omits the “ordered-output” logic of the original Card function – hence when you implement it that way, it could motivate each caller to implement their own “ordered-output” logic. And in case that’s a logic you want to exist in only one place, it would become a DRY violation
-
the Compound component pattern does not lead to a DRY violation per-se. In case you want users of such components to control the output order by themselves, using the pattern is ok.
-
Implementing this differently without the Compound component pattern will allow to keep the logic in the Card function and extract certain logic to smaller functions.
-
Whether this improves the readability for the very small, artificial example of this question, or not, is quite debatable – but when we assume the original code is more complex, the approach of extracting logic into smaller functions starts definitely making sense.
So in short, implement what you need. If a pattern fits to what you need, fine. If not, implement something different.
2
Only if you have more than one use of card with the same layout.
Let’s say you decide it is a violation of dry and you are going to fix it. But some users want a different order of fields in their cards.
You add a orderBy function to the props to allow this. If it’s not passed in the default order is used.
Your component is slightly better in that less code will be required for multiple usages with the default order. But slightly worse in the unconventional way of specifying the sub components.
The reason react gives you this syntax is so you can make you code look and behave more like HTML. I think people like this.