To keep in the spirit of honing our skills, we’ll tackle one more component, a button.
So let’s start with the basics, a <button />
has children
, an onClick
handler, a type
(submit
, button
or reset
) and can be disabled
.
One thing to keep in mind with buttons is that if you don’t supply a type, it will default to submit
which is generally not what you want.
import React, { ReactNode } from "react";
export type ButtonProps = {
children: ReactNode;
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
type: "submit" | "reset" | "button";
};
function Button(props: ButtonProps): JSX.Element {
const { children, disabled, onClick, type } = props;
return (
<button disabled={disabled} onClick={onClick} type={type}>
{children}
</button>
);
}
export default Button;
Using the Pick utility type we could shorten the ButtonProps
to the following:
export type ButtonProps = Pick<
React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>,
"children" | "disabled" | "onClick"
> & {
/**
* The reason we are not picking this is because react
* allows undefined.
*/
type: "button" | "submit" | "reset";
};
Touch
We just need to add that bootstrap flavor, let’s add variant
into the mix:
import React from "react";
import classNames from "classnames";
export type ButtonProps = Pick<
React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>,
"children" | "disabled" | "onClick"
> & {
/**
* The reason we are not picking this is because react
* allows undefined.
*/
type: "button" | "submit" | "reset";
variant:
| "primary"
| "secondary"
| "success"
| "danger"
| "warning"
| "info"
| "light"
| "dark"
| "link";
};
const BS_ROOT = "btn";
function Button(props: ButtonProps): JSX.Element {
const { children, disabled, onClick, type, variant } = props;
return (
<button
className={classNames(BS_ROOT, `${BS_ROOT}-${variant}`)}
disabled={disabled}
onClick={onClick}
type={type}
>
{children}
</button>
);
}
export default Button;
Satisfaction
But what if you wanted a “close” button which just an x
inside it as children?
import React from "react";
import Alert from "./components/Button";
function App(): JSX.Element {
return <Button type="button">×</Button>;
}
We should always have accessibility in mind when we create components, a button without a readable text is not accessible. The correct approach is to add an aria-label to the button.
And as an added advantage if you’re using React Testing Library, you can use a ByLabelText query and don’t have to add a
data-testid
to a button.
So we want to achieve this:
import React from "react";
import Alert from "./components/Button";
function App(): JSX.Element {
return (
<Button aria-label="Close" type="button">
×
</Button>
);
}
We can again pick the aria-label
from the button properties.
export type ButtonProps = Pick<
React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>,
"aria-label" | "children" | "disabled" | "onClick"
> & {
type: "button" | "submit" | "reset";
variant: "primary"; //shortened
};
const BS_ROOT = "btn";
function Button(props: ButtonProps): JSX.Element {
const {
"aria-label": ariaLabel,
children,
disabled,
onClick,
type,
variant,
} = props;
return (
<button
aria-label={ariaLabel}
className={classNames(BS_ROOT, `${BS_ROOT}-${variant}`)}
disabled={disabled}
onClick={onClick}
type={type}
>
{children}
</button>
);
}
That’s it, i hope you are digging the typescript/react series so far.