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.
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}
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;
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
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";
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";
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";
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"