shayan
Posted on August 14, 2020
React (also known as React.js or ReactJS) is an open-source JavaScript library for building user interfaces. React can be used as a base in the development of single-page or mobile applications. Regarding the React pattern, it brings simplicity and maintainability. Almost all projects I used, like SPA projects with React, or Next.js, and even ReactNative, I used these techniques to reduce code duplication, readability, and maintainability.
What exactly is a sub-component?
Sub-components mean combining a group component in one component, By using sub-components we can render the same view, but with a much more readable code and a reusable component. Sub-component can reduce a significant amount of code duplication and make your code so simple to read and understand.
Why do I need to use a sub-component in my project?
Of course, I believe that knowledge is a vital asset for every developer, but still we cannot find a use case, we barely use them during our daily development. As a result, let's find out why and when we need to use sub-component. To make it clear let's say we are going to implement a component to provide us Bootstrap card modules. So, let's first define what a Bootstrap card component contains. Well, a Bootstrap card component contains 3 parts, a header, a body, and a footer.
So if we suppose to build this component, it would be so simple,
//components/card.js
import React from 'react';
const Card = ({ cover_image, children, footer }) => (
<div className="card">
<img src={cover_image} className="card-img-top" />
<div className="card-body">
{children}
</div>
<div className="card-footer">
<small className="text-muted">{footer}</small>
</div>
</div>
);
export default Card;
And you can use it in an application like this
<Card
cover_image="https://dummyimage.com/400x120/dedede/000&text=cover image"
footer="Last updated 3 mins ago"
>
<h5 className="card-title">Card title</h5>
<p className="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.</p>
</Card>
But things get a bit hard when we need to add this feature in a way that footer and header get a DOM element, besides an image URL and text. So what can we do in that situation? Maybe one of the easiest ways is to pass a DOM element to footer and cover image or pass them another component, so our code would be like this
//components/card.js
const Card = ({ header = ‘’, children, footer = ‘’ }) => (
<div className="card">
{header}
<div className="card-body">
{children}
</div>
<div className="card-footer">
{footer}
</div>
</div>
);
// App.js
<Card
header={<img src="https://dummyimage.com/400x120/dedede/000&text=cover image" class="card-img-top" />}
footer={<div className="card-footer"><small class="text-muted">Last updated 3 mins ago</small></div>}
>
<h5 className="card-title">Card title</h5>
<p className="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.</p>
</Card>
Yep, works fine. But just to consider it as clean and readable, I think it would be a bit hard to read or maintain it if the footer and header element grows. In conclusion, here is exactly where we need a sub-component, we can rewrite the above component as below, which is more readable and of course clean. Means, besides passing footer and header to the component, we can pass them as the children within the tag of Card.Header
and Card.Footer
.
<Card>
<Card.Header>
<img src="https://dummyimage.com/400x120/dedede/000&text=cover image" class="card-img-top" />
</Card.Header>
<Card.Body>
<h5 className="card-title">Card title</h5>
<p className="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.</p>
</Card.Body>
<Card.Footer>
<small class="text-muted">Last updated 3 mins ago</small>
</Card.Footer>
</Card>
How to build a sub-component?
So let’s go add a sub-component step by step to our Card component. As it’s obvious, we somehow should build the Card.Header
, Card.Body
, and Card.Footer
and assign them to the Card component, then we should get them in the Card component and fill the component with their data. As React is a strong library, we can assign some parameter to the component, like below
import React from 'react';
const CustomComponent = ({...}) => (...);
CustomComponent.displayName = 'custom-component';
CustomComponent.SubComponent = AnotherComponent;
export default CustomComponent;
Dadaaaa, the secrets are revealed. So we should build Header, Footer, and Body as a component then assign them to the Card component, this way, they would be properties of our Card component. But how can we get them in the Card component so that we are able to render them in different parts? Don’t worry we will discuss this later, then stay tuned.
Firstly, let’s add Header
, Body
, and Footer
components to the Card component. But before it, let's practice a thing together, let's say we have an object name MyObject
which has a property named foo
.
const MyObject = {
foo: ‘bar’
};
console.log(MyObject.foo); // bar
We also can add another property to this object, and use it later.
const MyObject = {
foo: ‘bar’
};
MyObject.new_prop = ‘hello world’;
console.log(MyObject.new_prop); // bar
Dadaaaa, this is the approach we are going to do to add those above components to our Card component.
So we can define our sub-component and then assign them to the Card component, just like below.
import React from 'react';
const Card = ({ header = '', children, footer = '' }) => (
...
);
const Header = ({ children }) => children;
Card.Header = Header;
const Body = ({ children }) => children;
Card.Body = Body;
const Footer = ({ children }) => children;
Card.Footer = Footer;
export default Card;
Just like the above example, now, we have access to Card.Header
, Card.Body
, and Card.Footer
. So we can rewrite our code as below
<Card>
<Card.Header>
<img src="https://dummyimage.com/400x120/dedede/000&text=cover image" class="card-img-top" />
</Card.Header>
<Card.Body>
<h5 className="card-title">Card title</h5>
<p className="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This card has even longer content than the first to show that equal height action.</p>
</Card.Body>
<Card.Footer>
<small class="text-muted">Last updated 3 mins ago</small>
</Card.Footer>
</Card>
As we use our Card sub-component within the Card tag <Card>...</Card>
, they are detected as children for Card components, so we don’t have direct access to them, to get them as a prop and use them, wherever needed, for that important, we need to make a trick. We can assign a name to our sub-components by displayName
prop, then filter the children element of the Card component to find them. So we can implement it as below
const Header = ({ children }) => children;
Header.displayName = 'Header';
Card.Header = Header;
const Body = ({ children }) => children;
Body.displayName = 'Body';
Card.Body = Body;
const Footer = ({ children }) => children;
Footer.displayName = 'Footer';
Card.Footer = Footer;
And then our Card component would be like this.
import React from 'react';
const Card = ({ children }) => {
const header = React.Children.map(children, child => child.type.displayName === 'Header' ? child : null);
const body = React.Children.map(children, child => child.type.displayName === 'Body' ? child : null);
const footer = React.Children.map(children, child => child.type.displayName === 'Footer' ? child : null);
return (
<div className="card">
{header}
<div className="card-body">
{body}
</div>
<div className="card-footer">
{footer}
</div>
</div>
);
}
const Header = ({ children }) => children;
Header.displayName = 'Header';
Card.Header = Header;
const Body = ({ children }) => children;
Body.displayName = 'Body';
Card.Body = Body;
const Footer = ({ children }) => children;
Footer.displayName = 'Footer';
Card.Footer = Footer;
export default Card;
You can find the whole code here in this repository
Join the discussion
I would love to get some feedback here.
Posted on August 14, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 19, 2024
November 15, 2024