Modern React generics components
Programming
2/23/2024
3 minutes
Author: Dawid

Available languages:

Modern React generics components

There are many ways to create reusable components in React. From the all-too-familiar giving class for example: ".button" as css class preceded by ".button-secondary" etc. But are they suitable for modern React and large projects?

In this article, I would like to present the best, the most optimized and currently known to me option for creating generic Components using the example of a button.

For this particular example I used next.js as a starter project. To create our project we will need several libraries. The first of them will be the well-known TailwindCSS. It is crucial for assigning classes in our new generic component. Then we need to create a helper function called "cn":

import { clsx } from "clsx"
import type { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}


To make it work properly, with new Button Component we need to install the following: npm i clsx tailwind-merge class-variance-authority

Now we can get on with creating our generic Button Component. We will create it using the "class-variance-authority" library. It will allow us to create different variations of buttons to which we can assign TailwindCSS classes.

import * as React from "react";
import { cva } from "class-variance-authority";
import type { VariantProps } from "class-variance-authority";

import { cn } from "@/utils/cn";

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-base font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-orange-300 text-white hover:bg-orange-400",
secondary: "bg-blue-300 text-white hover:bg-blue-400",
ghost:
"border-orange-300 border-2 roundend-sm text-orange-300 hover:border-blue-400 hover:text-blue-400",
},
size: {
default: "px-4 py-2",
sm: "p-2 text-sm",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
const Comp = "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";

export { Button, buttonVariants };


Using the cva function, we can give various variants to our component from "default, secondary, ghost" through more generic variances like "size". Finally, we need to give our Component Interface for type-safety. This will make using our generic Component simple and intuitive throughout the project.

The last step includes the issue of using our new component. And it is trivially simple. Here it is enough to import the Button component from our built component, and it is ready to use:

<Button variant="default">Default</Button>

Or if we want to use a different variant:

<Button variant="secondary">Secondary</Button>

All code can be found on my github: https://github.com/shadown125/react-generic-components If you have any question or feedback, then feel free to comment.

About the author

avatar

Penify creator, Fullstack developer, Typescript, Web, Apps, Blogger

I am an experienced Fullstack web developer and the creator of Penify blog platform. I am excited and motivated to explore new technologies. I am a dedicated and hardworking person with a will to create the best user experience.

components
js
next.js
react
tailwind
tailwind-merge
ts

Comments

Be the first to comment on this article!