How to create React component properly
In this post we will take a look at how I created React component in the past and how I changed it. These changes are crucial for creating components efficiently, easy to maintenance and upgrade.
この記事の目次
Project initialization
I will use NextJS and Tailwind CSS for the demonstration. You can use below command for setting up the project. NodeJS, npm or yarn are required.
npx create-next-app@latest
#or
yarn create next-app
#classnames library for managing className
npm install -S classnames
#or
yarn add classnames
Component creation
The old way
I’m going to create React button component. This components characteristics are:
- Not using native element.
- Not extending native properties.
- Using too specific properties.
- Not exporting component properties.
import classNames from "classnames";
import { FC } from "react";
interface ButtonProps { //not exporting and extending button properties
className?: string;
text?: string;
isPrimary?: boolean;
isSecondary?: boolean; //too specific property for setting color
onClick?: () => void;
}
export const OldButton: FC<ButtonProps> = (props) => {
return (
<div
className={classNames(
"cursor-pointer text-sm text-center p-2 rounded",
{
"text-white bg-blue-500": props.isPrimary,
"text-white bg-green-500": props.isSecondary,
},
props.className
)}
onClick={props.onClick}
>
{props.text}
</div> //not using native element. Div element is used for button
);
}
The new way
It is recommended to use correct element when possible. Characteristics of the new way are:
- Using native element.
- Extending native properties.
- Using general properties.
- Exporting component properties.
import classNames from "classnames";
import { ButtonHTMLAttributes, FC } from "react";
export interface NewButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { //exporting and extending button properties
color?: "primary" | "secondary"; //using general property for setting color
}
export const NewButton: FC<NewButtonProps> = (props) => {
const {
color,
className,
...rest
} = props;
return (
<button
className={classNames(
"text-sm p-2 rounded",
{
"text-white bg-blue-500": color === "primary",
"text-white bg-green-500": color === "secondary",
},
className,
)}
{...rest}
/> //using button element
);
};
Render and result
Now you can render components.
<OldButton
className={"px-6"}
isPrimary={true}
text={"My old button"}
onClick={() => console.log("Clicked old button!")}
/>
<NewButton
className={"px-6"}
color={"primary"}
onClick={() => console.log("Clicked new button!")}
>
My new button
</NewButton>
As you can see, two components appearances are identical. But the code underneath is different, this affects development time and future extension.
Issue discussion
Using correct component and properties extension
We already have had the onClick() property on both old and new button, but the button is not always clickable. So we will need a property to disable them.
With the old way we need to add “disabled” property and fix the logic.
import classNames from "classnames";
import { FC } from "react";
interface ButtonProps {
className?: string;
text?: string;
isPrimary?: boolean;
isSecondary?: boolean;
isDanger?: boolean;
disabled?: boolean;//new property
onClick?: () => void;
}
export const OldButton: FC<ButtonProps> = (props) => {
return (
<div
className={classNames(
"text-sm text-center p-2 rounded",
{
"cursor-pointer": !props.disabled,//style for "disabled" property
"text-white bg-blue-500": props.isPrimary,
"text-white bg-green-500": props.isSecondary,
"text-white bg-red-500": props.isDanger,
},
props.className
)}
onClick={() => {
//new logic for "disabled" property
if (!props.disabled && props.onClick) {
props.onClick();
}
}}
>
{props.text}
</div>
);
}
With the new way we don’t have declare new property for disabling button. We also don’t have to fix cursor style and onClick logic. Button element handles these styles and logic internally.
Meaningful and extensible properties
The old way use two properties “isPrimary” and “isSecondary” for setting the color. This will cause issue if both properties are “true” and you have to add new property for new color like the code below (pseudo code).
isDanger?: boolean;
...
"text-white bg-red-500": props.isDanger,
With the new way we only need to add new option for the color and only one color can be set (pseudo code).
color?: "primary" | "secondary" | "danger";
...
"text-white bg-red-500": color === "danger",
Exporting properties
The old way does not export its interface. So when you want to add new feature for current component, you have to edit it. For example you want to add an icon on the left of the text (pseudo code).
leftIcon?: ReactNode;
...
{!props.leftIcon && props.text}
{props.leftIcon && (
<div className={"flex items-center gap-2"}>{props.leftIcon}{props.text}</div>
)}
Because the new way exports its interface so you can create a new icon button without editing current component.
import { FC } from "react";
import { NewButton, NewButtonProps } from "./NewButton";
export const NewIconButton: FC<NewButtonProps> = (props) => {
const { children, ...rest } = props;
return (
<NewButton {...rest}>
<div className={"flex items-center gap-2"}>{children}</div>
</NewButton>
);
};
Render two icon buttons and see the result.
<OldButton
className={"px-6"}
isSecondary={true}
text={"My old icon button"}
leftIcon={
<img
src={"/search.svg"}
width={24}
height={24}
/>
}
onClick={() => console.log("Clicked old icon button!")}
/>
<NewIconButton
className={"px-6"}
color={"secondary"}
onClick={() => console.log("Clicked new icon button!")}
>
<img
src={"/search.svg"}
width={24}
height={24}
/>
My new icon button
</NewIconButton>
Conclusion
The result display on browser looks the same for end users but the code is different for developers. I had used the old way for a long time, it’s fine when there aren’t many functions and logics. But nowadays, designers want to custom and put a lot of things into their component design. If we go with the old way, we will have to add a lot of properties and logics.
I’m using the new way now and I found it is convenient and easy to extend. I can make new component by extending current one. Let’s try the new way and see!
カテゴリー: