How To Build a Reusable Button Component in React

coding

Sometimes we want a component that looks like a button but functions like a link, such as a call-to-action in the header that takes a user to a contact page. In this post we will make a React button component that returns a link or button element based on the props passed to it.

Setup

If you don't already have a React project to work in, you can clone the source code here. The project is built using Vite so it has super quick spin up and reload times.

Create the Button Component

First we will create a Button folder inside a components folder. Inside there, we will add our Button.tsx and Button.module.css.

├── src │ ├── components │ │ ├── Button │ │ │ ├── Button.tsx │ │ │ ├── Button.module.css

Let's set up our component with an empty function and interface.

import classes from "./Button.module.css"; interface ButtonProps {} const Button = ({}: ButtonProps) => {}; export default Button;

We want the component to return a different element depending on certain props.

If to is passed, we will return a React Link element; if href is passed we will return a regular a element. Otherwise, we'll return a button.

So let's import Link, set up some types, and get our conditionals working. For the content of the button, we're going to pass children, so let's import PropsWithChildren from React.

import { Link } from "react-router-dom"; import { PropsWithChildren } from "react"; import classes from "./Button.module.css"; interface ButtonProps { to?: string; href?: string; onClick?: () => void; } const Button = ({ to, href, onClick, children, }: PropsWithChildren<ButtonProps>) => {};

There will be three different elements in this component, and we might want to share certain attributes among them. Let's create an object where we can add all the shared attributes, so if we need to change them, we can do it in one place.

const Button = ({ to, href, onClick, children, }: PropsWithChildren<ButtonProps>) => { const commonProps = { className: classes.button, }; };

Final Code

Now all that is left is to create the conditionals and return our elements with their specific attributes.

import { Link } from "react-router-dom"; import { PropsWithChildren } from "react"; import classes from "./Button.module.css"; interface ButtonProps { to?: string; href?: string; onClick?: () => void; } const Button = ({ to, href, onClick, children, }: PropsWithChildren<ButtonProps>) => { const commonProps = { className: classes.button, }; if (to) { return ( <Link to={to} {...commonProps}> {children} </Link> ); } if (href) { return ( <a href={href} target="_blank" rel="noopener noreferrer" {...commonProps}> {children} </a> ); } return ( <button onClick={onClick} {...commonProps}> {children} </button> ); }; export default Button;

Let's Check It Out

Let's try all the variations and see what ends up in the inspector.

Using the component with an on click event:

<Button onClick={() => console.log("hello")}>Click Me</Button>
<button class="_button_12alw_1">Click Me</button>

Linking out to an external site:

<Button href="https://google.com">Click Me</Button>
<a href="https://google.com" target="_blank" rel="noopener noreferrer" class="_button_12alw_1" > Click Me </a>

Linking to another page in our React project:

<Button to="/">Click Me</Button>
<a class="_button_12alw_1" href="/">Click Me</a>

Wrapping Up

That wraps up our button component. It's a minimal example that can be built upon in many ways, such adding an optional type attribute to the button or togglable classes. Let me know what you come up with!

Here is the full code for this project, and feel free to reach out on Twitter. Thank you for reading!