Push me

Last updated on May 30, 2020. Created on May 16, 2020.

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">&times;</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">
&times;
</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.

Next

Kiss my default props

Previous

Practice makes purrfect