Learn Ruby on Rails Book

Reusable Components

In React, a reusable component is a piece of UI that can be used in various parts of an application to build more than one UI instance. For instance, we can have a Button component which displays different action texts at different pages. Generally speaking, we can make a component more reusable by turning it from more specific to more generic.

While naming the components, we follow PascalCase for naming both the exported component as well as filename.

Custom Tailwind colors

Moving forward we will be using, custom defined color classes to style our components. In order to do so, add the following lines of code to tailwind.config.js:

1module.exports = {
2  future: {
3    // removeDeprecatedGapUtilities: true,
4    // purgeLayersByDefault: true,
5  },
6  purge: [],
7  theme: {
8    extend: {
9      colors: {
10        'bb-purple': '#5469D4',
11        'bb-env': '#F1F5F9',
12        'bb-border': '#E4E4E7',
13        'bb-gray-700': '#37415',
14        'bb-gray-600': '#4B5563',
15        'bb-red': '#F56565',
16        'bb-green': '#31C48D',
17        'bb-yellow': '#F6B100',
18        "nitro-gray-800": "#1F2937",
19      },
20      boxShadow: {
21        "custom-box-shadow": "10px 10px 5px 200px rgba(0,0,0,1)",
22      }
23    },
24  },
25  variants: {},
26  plugins: [],
27}

Button Component

Let's first create a Button component. To do so, run the following command

1mkdir -p app/javascript/src/components
2touch app/javascript/src/components/Button.jsx

Inside Button.jsx paste the following contents:

1import React from "react";
2import PropTypes from "prop-types";
3
4const Button = ({ type = "button", buttonText, onClick, loading }) => {
5  return (
6    <div className="mt-6">
7      <button
8        type={type}
9        onClick={onClick}
10        className="relative flex justify-center w-full px-4 py-2
11        text-sm font-medium leading-5 text-white transition duration-150
12         ease-in-out bg-bb-purple border border-transparent rounded-md
13         group hover:bg-opacity-90 focus:outline-none"
14      >
15        {loading ? "Loading..." : buttonText}
16      </button>
17    </div>
18  );
19};
20
21Button.propTypes = {
22  type: PropTypes.string,
23  buttonText: PropTypes.string,
24  loading: PropTypes.bool,
25  onClick: PropTypes.func,
26};
27export default Button;

Now, to import Button component in any of the views, we can simply import as follows at the top of the file:

1import Button from "components/Button";

First, let's create a NavItem reusable component. To do so, run the following command:

1mkdir -p app/javascript/src/components/NavBar/
2touch app/javascript/src/components/NavBar/NavItem.jsx

Inside NavItem.jsx, paste the following contents:

1import React from "react";
2import { Link } from "react-router-dom";
3
4const NavItem = ({ iconClass, name, path }) => {
5  return (
6    <Link
7      to={path}
8      className="inline-flex items-center px-1 pt-1 mr-3
9      font-semibold text-sm leading-5
10      text-indigo-500 hover:text-indigo-500"
11    >
12      {iconClass && <i className={`${iconClass} text-bb-purple`}></i>}
13      {name}
14    </Link>
15  );
16};
17
18export default NavItem;

Now, to create a NavBar component which will make use of the NavItems, run the following command:

1touch app/javascript/src/components/NavBar/index.jsx

The reasons, it's named index.jsx is because it's the root file which will be auto-imported in scenarios where we import like say import NavBar from '../NavBar'.

Inside index.jsx, paste the following contents

1import React from "react";
2import NavItem from "./NavItem";
3
4const NavBar = () => {
5  return (
6    <nav className="bg-white shadow">
7      <div className="px-2 mx-auto max-w-7xl sm:px-4 lg:px-8">
8        <div className="flex justify-between h-16">
9          <div className="flex px-2 lg:px-0">
10            <div className="hidden lg:flex">
11              <NavItem name="Todos" path="/dashboard" />
12              <NavItem
13                name="Create"
14                iconClass="ri-add-fill"
15                path="/tasks/create"
16              />
17            </div>
18          </div>
19          <div className="flex items-center justify-end">
20            <a
21             className="inline-flex items-center px-1 pt-1 text-sm
22             font-semibold leading-5 text-bb-gray-600 text-opacity-50
23             transition duration-150 ease-in-out border-b-2
24             border-transparent hover:text-bb-gray-600 focus:outline-none
25             focus:text-bb-gray-700 cursor-pointer"
26            >
27              LogOut
28            </a>
29          </div>
30        </div>
31      </div>
32    </nav>
33  );
34};
35
36export default NavBar;

Add remixicon to dependencies

1yarn add remixicon

The command listed above will add the remixicon package to the project, which will allow us to display icons in all the components. Besides, we also need to add the remixicon css to our javascript stylesheets, in order to properly render those icons:

Append the following lines to app/javascript/stylesheets/application.scss:

1@import url("https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css");

Now, add the following import statement inside App.jsx:

1import React, { useEffect } from "react";
2import { Route, Switch, BrowserRouter as Router } from "react-router-dom";
3
4const App = () => {
5  useEffect(() => {
6    initializeLogger();
7    logger.info("Log from babel js-logger");
8  }, []);
9
10  return (
11    <Router>
12      <Switch>
13        <Route exact path="/" render={() => <div>Home</div>} />
14        <Route exact path="/about" render={() => <div>About</div>} />
15      </Switch>
16    </Router>
17  );
18};
19
20export default App;
21

Container Component

To create a Container component, run the following command:

1touch app/javascript/src/components/Container.jsx

Now, open app/javascript/components/Container.jsx and paste the following content:

1import React from "react";
2import NavBar from "components/NavBar";
3
4import PropTypes from "prop-types";
5const Container = ({ children }) => {
6  return (
7    <>
8      <NavBar />
9      <div className="px-4 py-2 mx-auto max-w-7xl sm:px-6 lg:px-8">
10        <div className="max-w-3xl mx-auto">{children}</div>
11      </div>
12    </>
13  );
14};
15
16Container.propTypes = {
17  children: PropTypes.node.isRequired,
18};
19
20export default Container;

To import Button component in any of the views, we can simply run

1import Button from "components/Button";

Input Component

To create an Input component, run the following command

1touch app/javascript/src/components/Input.jsx

Inside Input.jsx, paste the following content:

1import React from "react";
2import PropTypes from "prop-types";
3
4const Input = ({
5  type = "text",
6  label,
7  value,
8  onChange,
9  placeholder,
10  required = true,
11}) => {
12  return (
13    <div className="mt-6">
14      {label && (
15        <label className="block text-sm font-medium
16              leading-5 text-bb-gray-700">
17          {label}
18        </label>
19      )}
20      <div className="mt-1 rounded-md shadow-sm">
21        <input
22          type={type}
23          required={required}
24          value={value}
25          onChange={onChange}
26          placeholder={placeholder}
27          className="block w-full px-3 py-2 placeholder-gray-400
28          transition duration-150 ease-in-out border
29          border-gray-300 rounded-md appearance-none
30          focus:outline-none focus:shadow-outline-blue
31          focus:border-blue-300 sm:text-sm sm:leading-5"
32        />
33      </div>
34    </div>
35  );
36};
37
38Input.propTypes = {
39  type: PropTypes.string,
40  label: PropTypes.string,
41  value: PropTypes.node,
42  placeholder: PropTypes.string,
43  onChange: PropTypes.func,
44  required: PropTypes.bool,
45};
46
47export default Input;

You can import an Input component by using

1import Input from "components/Input";

Table Component

To create a Table component we will create a TableRow and TableHeader component, as part of our making component reusable pattern. To do so, run the following command:

1mkdir -p app/javascript/src/components/Tasks/Table/
2touch app/javascript/src/components/Tasks/Table/TableRow.jsx

Inside TableRow.jsx, paste the following content

1import React from "react";
2import PropTypes from "prop-types";
3
4const TableRow = ({ data, destroyTask, updateTask }) => {
5  return (
6    <tbody className="bg-white divide-y divide-gray-200">
7      {data.map((rowData) => (
8        <tr key={rowData.title}>
9          <td className="px-6 py-4 text-sm font-medium
10            leading-5 text-bb-gray whitespace-no-wrap">
11            {rowData.title}
12          </td>
13          <td className="px-6 py-4 text-sm font-medium
14            leading-5 text-bb-gray whitespace-no-wrap">
15            {rowData.user_id}
16          </td>
17          <td className="px-6 py-4 text-sm font-medium
18            leading-5 text-right cursor-pointer">
19            <a className="text-bb-purple text-opacity-50
20              hover:text-opacity-100">
21              Edit
22            </a>
23          </td>
24          <td className="px-6 py-4 text-sm font-medium
25            leading-5 text-right cursor-pointer">
26            <a className=" hover:text-bb-red">Delete</a>
27          </td>
28        </tr>
29      ))}
30    </tbody>
31  );
32};
33
34TableRow.propTypes = {
35  data: PropTypes.array.isRequired,
36  destroyTask: PropTypes.func,
37  updateTask: PropTypes.func,
38};
39
40export default TableRow;

Then, create TableHeader component by running the following command:

1touch app/javascript/src/components/Tasks/Table/TableHeader.jsx

Inside TableHeader.jsx, parse the following contents

1import React from "react";
2import { compose, head, join, juxt, tail, toUpper } from "ramda";
3
4const TableHeader = () => {
5  return (
6    <thead>
7      <tr>
8        <th className="w-1"></th>
9        <th className="px-6 py-3 text-xs font-bold leading-4 tracking-wider
10        text-left text-bb-gray-600 text-opacity-50 uppercase bg-gray-50">
11          Title
12        </th>
13        <th className="px-6 py-3 text-sm font-bold leading-4 tracking-wider
14        text-left text-bb-gray-600 text-opacity-50 bg-gray-50">
15          Assigned To
16        </th>
17        <th className="px-6 py-3 bg-gray-50"></th>
18      </tr>
19    </thead>
20  );
21};
22
23export default TableHeader;

Now, create an index.jsx file inside Table folder by running the following command

1touch app/javascript/src/components/Tasks/Table/index.jsx

Paste the following contents inside Table/index.jsx:

1import React from "react";
2
3import TableHeader from "./TableHeader";
4import TableRow from "./TableRow";
5
6const Table = ({ data, destroyTask, updateTask }) => {
7  return (
8    <div className="flex flex-col mt-10 ">
9      <div className="my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
10        <div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
11          <div className="overflow-hidden border-b border-gray-200 shadow md:custom-box-shadow">
12            <table className="min-w-full divide-y divide-gray-200">
13              <TableHeader />
14              <TableRow data={data} />
15            </table>
16          </div>
17        </div>
18      </div>
19    </div>
20  );
21};
22
23export default Table;

Now, Table can be imported by using

1import Table from "components/Tasks/Table";

Page Loader Component

So when there occurs situations where we need to make the user understand that we are making an api call and the data is not yet loaded, usually we show a spinner or use a custom PageLoader component. To create a PageLoader component, run the following command:

1touch app/javascript/src/components/PageLoader.jsx

Inside PageLoader.jsx, paste the following content

1import React from "react";
2
3const PageLoader = () => {
4  return (
5    <div className="flex flex-row items-center justify-center w-screen h-screen">
6      <h1 className="text-lg leading-5">Loading...</h1>
7    </div>
8  );
9};
10
11export default PageLoader;

To use the loader component use the following

1import PageLoader from "components/PageLoader";

Now let's commit these changes:

1git add -A
2git commit -m "Added reusable components"

References