Storybook & Atomic Design – 1.5. – Building Our Button

In this lesson, we'll be revisiting our Button component. Adding new properties that we will process and document in our pattern library.

A refreshing reminder, the Button component falls under the concept of an atom as it is a low-level component that can be used in many other components.

Initially, we will focus on passing three props to our Button component. These properties include -

  1. children - node
  2. onClick - function
  3. href - string

All of these props will be optional for the time being.

Depending on if we pass a value to them or not, we will dynamically return an HTML button or anchor element to our application.

Button Component

- __Storybook__
   - __components__
     - __atoms__
       - __button__
         - button.jsx
         - button.stories.js
import React from 'react';

const Button = ({ children, href, onClick }) => (
    <button className="button" onClick={onClick}>{children}</button>
);

export default Button;

children Prop

We've previously covered the children prop when creating our first Storybook story.

children refers to the inner content of any component.

This could include children components, text, and any associated logic to the children referenced.

import React from "react";
import Button from "./button";

export const basicButton = () => <Button>Basic button</Button>;

export default {
    component: Button,
    title: "Button"
};

onClick Prop

The second prop we will be using in our component is the onClick prop.

onClick will expect us to pass in a function to the component. This function will be called anytime we click on the button or submit the button in our application.

It would be used by writing a layout using an example similar to -

import React from "react";
import Button from "./button";

const alertText = e => {
    e.preventDefault();
    alert("You clicked the button");
}

export const basicButton = () => <Button>Basic button</Button>;
export const functionButton = () => <Button onClick={alertText}>Click me</Button>

export default {
    component: Button,
    title: 'Button',
};

href Prop

We finally have a href prop that we can pass to the Button component when we want the component to return an anchor tag element instead of a button element. We'll be swapping out the element returned by our component based on if we have a href value available to us.

import React from "react";
import Button from "./button";

const alertText = e => {
    e.preventDefault();
    alert("You clicked the button");
}

export const basicButton = () => <Button>Basic button</Button>;
export const functionButton = () => <Button onClick={alertText}>Click me</Button>
export const linkedButton = () => <Button href="/route">Link to route</Button>

export default {
    component: Button,
    title: 'Button',
};

Our Button Stories

We now have three stories of our Button component available in Storybook.

  1. Basic Button
  2. Function Button
  3. Linked Button

Each story has a unique instance of the Button component being used and helps document the different ways in which we can use our Button within the context of a React application.

Swapping our Returned Markup Based on Props

At the moment when we pass in our href value to our Button component, it won't do anything to the HTML we return. This is good because the last thing we want to do is pass in the href value to a standard HTML button element, as it isn't a valid value and the browser won't use it.

Instead, we'll have to rework our Button component to use a JavaScript if statement to check if there is or isn't a href value defined before returning markup to our application.

I'm a big fan of using inverted if statements where we check for falsey values instead of true values and return markup based on that. This approach prevents nested if statements and is something I picked up from reading articles on how to write cleaner code.

It can take a while to get used to and is completely optional, you can use a standard check for true instead of falsey and the code should work as expected.

So in our Button component, we now check if a href value has been defined. If it hasn't, we know to call on the browsers HTML button element and attach an onClick function if available.

If a href value has been defined, we will not return the button and continue with the logic in the javascript scope to return an anchor element with a href value passed in from the Button props.

import React from "react";

const Button = ({ children, href, onClick }) => {
    if (!href)
        return (
            <button className="button" onClick={onClick}>
                {children}
            </button>
        );
    return (
        <a className="button" href={href}>
            {children}
        </a>
    );
};

export default Button;

PropTypes (Optional)

This part of the course is optional but will return benefits as your applications scales.

PropTypes is a React package that allows us to define the types of data we expect to be passed as props to our React components. It's very similar to TypeScript and other type languages, however, I feel that PropTypes is easier for those unfamiliar with typed languages to be introduced to the concept slowly and optionally.

To install PropTypes, run the following command in your terminal -

npm i prop-types

We can include PropTypes in our Button component fairly easily as we know what props we expect the Button to contain and what types of data the props will be.

import React from "react";
import { func, node, string } from "prop-types";

const Button = ({ children, href, onClick }) => {
	if (!href)
		return (
			<button className="button" onClick={onClick}>
				{children}
			</button>
		);
	return (
		<a className="button" href={href}>
			{children}
		</a>
	);
};

// Expected prop values
Button.propTypes = {
  children: node.isRequired,
  href: string,
  onClick: func,
}

// Default prop values
Button.defaultProps = {
  children: 'Button text'
};

export default Button;

To get started with PropTypes, we'll first import the React package from prop-types.

Then before our export statement, we are going to add two keys to our Button component.

The first set of values will define the types of props we accept to our Button component.

As previously mentioned at the start of the post, we have children which would include a node and potentially children nodes, href which is a string value, and finally an onClick prop which will contain a function.

One thing to note is that the children prop is set to isRequired which means we must always specify a value for children when referencing our Button component.

Why Use a Typed Language or Package?

By defining the types of props we expect, we can prevent issues where for example the Button component is called upon and provided an integer value for the href prop. Instead, if this were to happen we would get a warning telling us that an invalid value has been provided, preventing the component being used incorrectly in an application later on.

As well as setting the types of props we can come to expect, we can set default values with defaultProps properties. For our Button component, we will only specify a default value for the children prop as we will always want a value available to us in our component.

If we reference the Button component anywhere in our applications it will have the text 'Button text' set as the inner HTML for the button element, this can be overwritten by passing any node value as a prop to the component.

In the next lesson, we'll be focusing on styling the Button element to look like the buttons designed in the original Figma interface designs.

Continue Reading 📚