Skip to content

Commit

Permalink
feat: update fully-reusable-components to use interface
Browse files Browse the repository at this point in the history
  • Loading branch information
theodorusclarence committed Apr 21, 2024
1 parent af1d724 commit 51434de
Showing 1 changed file with 22 additions and 15 deletions.
37 changes: 22 additions & 15 deletions src/contents/blog/fully-reusable-components.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: 'How to Create Fully Reusable React Components'
publishedAt: '2023-12-14'
lastUpdated: '2024-04-21'
description: 'Creating a component is fairly easy, but doing them correctly is a different story.'
englishOnly: 'true'
banner: 'jeremy-bishop-QUwLZNchflk-unsplash'
Expand Down Expand Up @@ -105,15 +106,15 @@ Well, you could create two brand new components called `FeatureCard` and `Confet
Usually, when have this kind of situation, we rely upon custom props for each condition. But it goes downhill pretty quickly as the requirements grow.

```tsx {3-4,6-7,17-19,22}
type CardProps = {
interface CardProps {
product: Product;
isFeatured?: boolean;
shootsConfetti?: boolean;

isFeaturedThreeColumns?: boolean;
isFeaturedButMakeItPop?: boolean;
// 20 other props that your designer needs
};
}

function Card({ product, ...props }: CardProps) {
return (
Expand Down Expand Up @@ -150,9 +151,9 @@ Don’t worry. We have a type for that, may I introduce `React.ComponentPropsWit
`React.ComponentPropsWithoutRef` also applies to any element you’re using: `<'input'>`, `<'button'>`, anything!

```tsx showLineNumbers /React.ComponentPropsWithoutRef<'div'>/ /...rest/
type CardProps = {
interface CardProps extends React.ComponentPropsWithoutRef<'div'> {
product: Product;
} & React.ComponentPropsWithoutRef<'div'>;
}

// first we grab the rest of the props using rest parameter,
function Card({ product, ...rest }: CardProps) {
Expand Down Expand Up @@ -206,13 +207,13 @@ While it is a good practice to spread all of the props, do note that **not all c
For example, a `ProductList` component that renders a list of products, probably only needs to be customizable in the `className` props. You can use `Pick` for that, and take only the `className` props.

```tsx /Pick/
type ProductListProps = Pick<
React.ComponentPropsWithoutRef<'div'>,
'className'
>;
interface ProductListProps
extends Pick<React.ComponentPropsWithoutRef<'div'>, 'className'> {
product: Product;
}

// No need to spread since we only need className
export default function ProductList({ className }: ProductListProps) {
export default function ProductList({ className, products }: ProductListProps) {
return (
<div className={cn(['grid-cols-3', className])}>
{products.map((product, i) => (
Expand All @@ -232,6 +233,12 @@ Some components that might need to be fully reusable are:

Do emphasize on the word **reusable** here. Make sure you're using this '_fully reusable component_' concept to a reusable component.

### Using Interface vs Type

Based on [Matt Pocock's blog](https://www.totaltypescript.com/react-apps-ts-performance?ref=theodorusclarence.com), `interface Props extends .. {}{:ts}` is slightly faster than `type Props = .. & {}{:ts}`.

Use `interface extends{:ts}`.

## Common Pitfalls & Solutions

By using fully reusable components, there are some pitfalls that you might encounter. I compiled some of them along with the solutions that I came up with.
Expand Down Expand Up @@ -293,9 +300,9 @@ So we now can merge conflicts and compose classes neatly.
You can safely customize your component now!

```tsx /cn/
type CardProps = {
interface CardProps extends React.ComponentPropsWithoutRef<'div'> {
product: Product;
} & React.ComponentPropsWithoutRef<'div'>;
}

function Card({ product, className, ...rest }: CardProps) {
return (
Expand All @@ -313,9 +320,9 @@ When you start to have more items inside the component, it can be quite confusin
Let’s say our `Card` component has a title, description, and image. We already use `className` props for the wrapper div. How can we customize the title class?

```tsx
type CardProps = {
interface CardProps extends React.ComponentPropsWithoutRef<'div'> {
product: Product;
} & React.ComponentPropsWithoutRef<'div'>;
}
function Card({ product, className, ...rest }: CardProps) {
return (
<div className={cn('mt-4', className)} {...rest}>
Expand All @@ -333,14 +340,14 @@ function Card({ product, className, ...rest }: CardProps) {
In my experience, I always use the normal `className` for the outermost element (wrapper). The solution is to create another object for a specific element that I might need.

```tsx {3-7}
type CardProps = {
interface CardProps extends React.ComponentPropsWithoutRef<'div'> {
product: Product;
classNames?: {
title?: string;
description?: string;
image?: string;
};
} & React.ComponentPropsWithoutRef<'div'>;
}
function Card({ product, className, classNames, ...rest }: CardProps) {
return (
<div className={clsx('mt-4', className)} {...rest}>
Expand Down

0 comments on commit 51434de

Please sign in to comment.