Skip to main content

Generic React Components

Using generic components

To avoid having to use specificity in the design of your components (i.e. creating CloseButtons, SubmitButtons and CancelButtons), it is best to learn how to create reusable, generic components.

Instead of using multiple 'similar-but-not-the-same' buttons, you can make one button that changes its behaviour or styling based on the props you pass to it.

Generic buttons

For example. You might want a nice, eye-catching, green button with the text "Add New Event". You could create a new React Component that does exactly that, but then you will have a large list of buttons in your repo that all do something different.

Instead, you'd probably want to have a button that is styled to be a confirmation button. A great practice is to name buttons based on priority. The often used 'confirmation button' will be refered to as primary. The button used to move back to the previous screen or abort an action is typically refered to as the secondary button.

An example is that you base the styling of the button on the string property variant.

info

Preferably do not use type or style as property names, as this can cause nasty bugs because these names are reserved for HTML and CSS properties.

Here's a small snippet of what that would look like:

/Button.tsx
import styles from "./Button.module.css"

interface ButtonProps {
label: string;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
variant?: "primary" | "secondary";
}

export const Button = ({ label, onClick, variant = "primary" }: ButtonProps) => (
<button className={`${variant === "primary" ? styled.primary : styles.secondary}`} onClick={onClick}>{label}</button>
)
/Button.module.css
.primary {
align-items: center;
color: tomato;
display: flex;
justify-content: center;
}

.primary:hover {
background: tomato;
}

.secondary {
composes: primary;
border: 1px solid tomato;
color: white;
}

Now, with this generic button you can always pass props about what should happen onClick, what type of button it is, and what it should say on the button itself. If you define a button somewhere to be a secondary button, it will appear with a different styling.

For future reference, think about what parts of your design can be made generic and how they can be used in different scenarios.

The children prop

To create even more customizability for your component, you may use children as a prop. This prop allows you to pass child components to, in this case, the button component. As a result, it is possible to have a variety of options for your button. You may inlude just a label, an icon, or both. Or you can also easily switch the order of the children. That makes the children prop very useful and clean.

Let's apply:

/Button.tsx
import { ReactNode } from 'react';

import styles from "./Button.module.css"

interface ButtonProps {
children: ReactNode;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
variant?: "primary" | "secondary";
}

export const Button = ({ children, onClick, variant = "primary" }: ButtonProps) => (
<button className={`${variant === "primary" ? styled.primary : styles.secondary}`} onClick={onClick}>{children}</button>
)

We removed the label and added the children prop instead. The TypeScript type for children is the ReactNode type, as it accepts React elements (JSX) or an array of React element. An instance of this component might look like this:

<Button onClick={addCalendarEvent}>
<Image alt="Icon of a plus sign" height="16" src="/add.svg" width="16">
Add event
<Button>
note

There are a number of implementation strategies a developer can choose for making components generic. The best strategy is highly context-dependent. Nonetheless it is also possible that different strategies will do the job for the same project.