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
.
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:
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>
)
.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:
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>
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.