0%

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.

When i want to practice componentization, i usually look for a candidate in a library like Bootstrap. The nice thing about these exercises is that if you create the same component more than once, you can gradually see the progress you’ve made. This time we’ll practice on the Alert component.

Basic

So according to the documentation an alert, in it’s most basic form, looks like this:

<div class="alert alert-primary" role="alert">
  A simple primary alert—check it out!
</div>
  • a div with alert role
  • root class: alert
  • contextual classes: primary, secondary, success, danger, warning, info, light and dark (alert-{contextual} class)

Result

import React, { ReactNode } from 'react';

export type AlertProps = {
  children: ReactNode;
  variant:
    | 'primary'
    | 'secondary'
    | 'success'
    | 'danger'
    | 'warning'
    | 'info'
    | 'light'
    | 'dark';
};

const BS_ROOT = 'alert';

function Alert({ children, variant }: AlertProps): JSX.Element {
  return (
    <div role="alert" className={`${BS_ROOT} ${BS_ROOT}-${variant}`}>
      {children}
    </div>
  );
}

export default Alert;

Usage

import React from 'react';
import Alert from './components/Alerts/Alert';

function App(): JSX.Element {
  return <Alert variant="primary">A simple primary alert—check it out!</Alert>;
}

Heading

<div class="alert alert-success" role="alert">
  <h4 class="alert-heading">Well done!</h4>
  <p>
    You made it.
  </p>
</div>

As there is no extra behavior tied to the heading, a string property looks like a perfect match otherwise an utility component <AlertHeader /> would have been in order.

Result

import React, { ReactNode } from 'react';

export type AlertProps = {
  children: ReactNode;
  heading?: string;
  variant:
    | 'primary'
    | 'secondary'
    | 'success'
    | 'danger'
    | 'warning'
    | 'info'
    | 'light'
    | 'dark';
};

const BS_ROOT = 'alert';

function Alert({ children, variant, heading }: AlertProps): JSX.Element {
  return (
    <div role="alert" className={`${BS_ROOT} ${BS_ROOT}-${variant}`}>
      {heading && <h4 className={`${BS_ROOT}-heading`}>{heading}</h4>}
      {children}
    </div>
  );
}

export default Alert;

And if you do need to have more fine-grained control of the html of the heading, you could transform the heading property into a slot, by changing the type from string to ReactNode.

Usage

import React from 'react';
import Alert from './components/Alerts/Alert';

function App(): JSX.Element {
  return (
    <Alert heading="Well done!" variant="success">
      <p>You made it!</p>
    </Alert>
  );
}

Dismissing

<div class="alert alert-warning alert-dismissible fade show" role="alert">
  <strong>Holy guacamole!</strong> You should check in on some of those fields
  below.
  <button type="button" class="close" data-dismiss="alert" aria-label="Close">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
  • a button with the class close
  • the alert needs to be enriched with the alert-dismissible class
  • when the user clicks on the dismiss button the alert should no longer be visible

I choose not to use the Bootstrap JS Library and let the alert itself control the dismissed state, if you ever need to do something more than just hiding the alert, you can always add an optional onAfterDismiss function property.

Result

import React, { ReactNode, useState } from 'react';
import classNames from 'classnames';

export type AlertProps = {
  children: ReactNode;
  dismissible?: boolean;
  heading?: string;
  variant:
    | 'primary'
    | 'secondary'
    | 'success'
    | 'danger'
    | 'warning'
    | 'info'
    | 'light'
    | 'dark';
};

const BS_ROOT = 'alert';

function Alert({
  children,
  dismissible,
  heading,
  variant,
}: AlertProps): JSX.Element {
  const [dismissed, setDismissed] = useState(false);

  return (
    <div
      className={classNames(BS_ROOT, `${BS_ROOT}-${variant}`, {
        [`${BS_ROOT}-dismissible`]: dismissible,
      })}
      style={{ display: dismissed ? 'none' : undefined }}
      role="alert"
    >
      {heading && <h4 className={`${BS_ROOT}-heading`}>{heading}</h4>}
      {children}
      {dismissible && (
        <button
          type="button"
          className="close"
          aria-label="Close"
          onClick={() => setDismissed(true)}
        >
          <span aria-hidden="true">&times;</span>
        </button>
      )}
    </div>
  );
}

export default Alert;

Usage

import React from 'react';
import Alert from './components/Alerts/Alert';

function App(): JSX.Element {
  return (
    <Alert variant="warning" dismissible>
      <strong>Holy guacamole!</strong> You should check in on some of those
      fields below.
    </Alert>
  );
}

If you are interested in my setup or in the tests using React Testing Library you can take a look at my React Playground Repository.

As Javascript continues to be one of the most misunderstood languages on the planet, of which you could find evidence all over the web. One of my personal favorites is the lightning Wat talk by Gary Bernhardt.

Lately i’ve begun to realize the underlying beauty of the language and felt the need to blog about it.

We’ll start with two primitives, undefined and null. Why do they exist both and what differentiates them?

undefined

The value that gets assigned to unassigned declared variables or to function parameters that are missing.

// 1. Unassigned variable
let variable;
console.log(variable); // => undefined

// 2. Missing function parameter
function greet(name) {
  console.log(name);
}

greet(); // => undefined

null

Represents the intentional absence of a value, it’s a semantical difference.

const notDotted = 'googlebe';
console.log(notDotted.match(/\w+\.\w+/)); // => null

Are they equal?

console.log(undefined === null); // => false

So strictly speaking they are different, which we could have expected. But they do have the same meaning:

console.log(undefined == null); // => true

Falsy

Luckily they both evaluate to false, otherwise we would have to write the following code:

let user;

// 💩 If they didn't evaluate to false and without ==
if (user !== null && user !== undefined) user.rename('John', 'Doe');

// 💪 With == (type coercion)
if (user != null) user.rename('John', 'Doe');

// 🙏 Thanks to falsy
if (user) user.rename('John', 'Doe');

Conclusion

Concepts like type coercion and falsy start to make a whole lot of sense if you look at the problems they fix.

In React Testing Library, the recommended way, after the other queries don’t work for your use-case, is to add a data-testid attribute on the element.

This works for all baked-in React HTML components, for instance on a <div/>:

import React from 'react';

export default function Component() {
  return <div data-testid="some-test-id" />;
}

Spec

import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import { render } from '@testing-library/react';
import Component from './Component';

test('it renders as expected', () => {
  const { getByTestId } = render(<Component />);

  expect(getByTestId('some-test-id')).toBeInTheDocument();
});

How to do this with custom components?

Due to the - in the name it’s not so easy and you will see other property names for it or just hard-coding the the testId into the component:

import React from 'react';
import { string } from 'prop-types';

export default function Component({ testId }) {
  return <div data-testid={testId} />;
}

Component.propTypes = {
  testId: string,
};

Component.defaultProps = {
  testId: undefined,
};

Spec

test('it renders as expected', () => {
  const testId = 'some-test-id';

  const { getByTestId } = render(<Component testId={testId} />);

  expect(getByTestId(testId)).toBeInTheDocument();
});

Preferred approach

As i don’t like having two different conventions for specifying the data-testid attribute, i would suggest the following:

import React from 'react';
import { string } from 'prop-types';

export default function Component({ 'data-testid': dataTestId }) {
  return <div data-testid={dataTestId} />;
}

Component.propTypes = {
  'data-testid': string,
};

Component.defaultProps = {
  'data-testid': undefined,
};

Spec

test('it renders as expected', () => {
  const testId = 'some-test-id';

  const { getByTestId } = render(<Component data-testid={testId} />);

  expect(getByTestId(testId)).toBeInTheDocument();
});

Now you can use data-testid for baked-in HTML components as well as your custom components.

Has it been almost three years since my last blog post 🤦‍♂️?

Up until the very beginning of 2016 i was mostly doing .Net Development using frameworks such as Microsoft WebAPI, Microsoft ASP.NET MVC. But years before that, an itch developed.

Around 2012 my interest grew into dynamic languages and i started to experiment and play with Ruby. If i look back, i think it was mostly the fact that it felt so weird to lay a typed contract over a dynamic format like JSON.

On the other hand our codebases started to grow on the client side. We were using frameworks like JQuery and JQuery.Ui to make things more ux friendly. In the end it looked like we had to create a controller in Javascript to keep it well structured.

In the beginning of 2016 i got the opportunity to become lead of a new project for a client. The choice was made to build it entirely using Javascript, by using Angular.JS for the frontend and NODE.JS for backend.

I never looked back, now my preferred stack is React.JS and Node.JS on the back-end and decided to pick up blogging again!