Tutorial Inertia Roles & Permissions - #8 - Membuat Reusable Component Dengan React

Artikel ini merupakan series dari Tutorial Laravel Inertia Roles & Permissions, disini kita akan belajar membuat sebuah reusable component menggunakan react.

Rafi Taufiqurrahman
Dipublish 15/07/2024

Pendahuluan

Pada project kali ini, kita akan membuat beberapa component menggunkan reactjs, tujuannya agar kita dapat membuat reusable UI (User Interface) yang dapat digunakan secara fleksibel. Component juga dapat digunakan untuk memisahkan logika tampilan dan logika dari apilkasi utama, sehingga mempermudah dalam pengembangan, pemeliharaan, dan pengujian kode.

Installasi Package Tambahan

Dikarenakan kita akan menggunakan sweetAlert dan package lain disini kita akan lakukan installasi terlebih dahulu, silahkan teman teman jalankan perintah berikut ini.

Terminal
npm install clsx sweetalert2 @tabler/icons-react

Pada perintah diatas kita melakukan installasi 3 buah package diantaranya sebagai berikut :

  • clsx
  • sweetAlert
  • @tabler/icons-react

Berkenalan dengan Props

Props merupakan sebuah element penting di react.js, kegunaanya adalah untuk mengirimkan data dari sebuah komponen ke komponen lainnya. Props sendiri dapat digunakan untuk mengirimkan data dalam bentuk string, object , array, method dan bahkan komponen lainnya. contohnya kurang lebih seperti berikut ini :

Catatan : contoh ini tidak perlu diikuti karena kita tidak akan menggunakan component tersebut pada project kita.

Disini kita akan membuat sebuah component yang kita beri dengan nama Label.jsx.

Label.jsx
import React from 'react'

export default function Label({text}) {
  return (
    <div>{text}</div>
  )
}

Pada component diatas kita membuat sebuah react functional component dengan nama label, kemudian kita masukan sebuah props dengan nama text.

Selanjutnya ketika kita menggunakan component label dan kita mendefiniskan sebuah props text maka component label akan dirender dengan props yang kita kirimkan, contohnya kurang lebih seperti berikut ini :

App.jsx
import './App.css'
import Label from './components/Label'

function App() {
  return (
    <>
      <Label text={'Belajar Props Jurnalkoding.com'}/>
    </>
  )
}

export default App

Maka output yang dihasilkan kurang lebih seperti gambar dibawah ini :

props-data

Setelah mengenal tentang props, disini kita akan lanjutkan untuk membuat beberapa component yang nantinya akan kita gunakan didalam project kita.

Component Card

Silahkan teman - teman buat file baru dengan nama Card.jsx yang diletakan didalam folder app/resources/js/Components, kemudian masukan kode berikut ini.

Card.jsx
import React from 'react'

export default function Card({ title, children, className }) {
    return (
        <>
            <div className={`p-4 rounded-t-lg border ${className} bg-white`}>
                <div className='flex items-center gap-2 font-semibold text-sm text-gray-700 capitalize'>
                    {title}
                </div>
            </div>
            <div className='bg-white p-4 border border-t-0 border-b rounded-b-lg'>
                {children}
            </div>
        </>
    )
}

Pada kode diatas, pertama kita lakukan import React terlebih dahulu.

Card.jsx
import React from 'react'

Selanjutnya kita membuat sebuah React Functional Component dengan beberapa props diantara-nya title, children dan className.

Card.jsx
export default function Card({ title, children, className })

Component Input

Silahkan teman - teman buat file baru dengan nama Input.jsx yang diletakan didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

Input.jsx
import React from 'react'

export default function Input({label, type, className, errors, ...props}) {
    return (
        <div className='flex flex-col gap-2'>
            <label className='text-gray-600 text-sm'>
                {label}
            </label>
            <input
                type={type}
                className={`w-full px-4 py-2 border text-sm rounded-md focus:outline-none focus:ring-0 bg-white text-gray-700 focus:border-gray-200 border-gray-200 ${className}`}
                {...props}
            />
            {errors && (
                <small className='text-xs text-red-500'>{errors}</small>
            )}
        </div>
    )
}

Pada kode diatas, pertama kita lakukan import React terlebih dahulu.

Input.jsx
import React from 'react'

Selanjutnya kita membuat sebuah React Functional Component dengan beberapa props diantara-nya label, type, className, errors dan ...props.

Input.jsx
export default function Input({label, type, className, errors, ...props})

Component Checkbox

Silahkan teman - teman buka file dengan nama Checkbox.jsx yang terletak didalam folder app/resources/js/Components, kemudian replace dengan kode berikut ini :

Checkbox.jsx
export default function Checkbox({ label, ...props }) {
    return (
        <div>
            <div className="flex flex-row items-center gap-2">
                <input
                    {...props}
                    type="checkbox"
                    className={'rounded-md bg-white border-gray-200 checked:bg-teal-500'}
                />
                <label className="text-sm text-gray-700">{label}</label>
            </div>
        </div>
    );
}

Pada kode diatas, pertama kita lakukan import React terlebih dahulu.

Checkbox.jsx
import React from 'react'

Selanjutnya kita membuat sebuah React Functional Component dengan beberapa props diantara-nya label dan ...props.

Checkbox.jsx
export default function Checkbox({ label, ...props })

Component Container

Silahkan teman - teman buat file baru dengan nama Container.jsx yang terletak didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

Container.jsx
import React from 'react'

export default function Container({children}) {
    return (
        <div className="py-12">
            <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                {children}
            </div>
        </div>
    )
}

Pada kode diatas, pertama kita lakukan import React terlebih dahulu.

Container.jsx
import React from 'react'

Selanjutnya kita membuat sebuah React Functional Component dengan sebuah props children.

Container.jsx
export default function Container({children}) 

Component Textarea

Silahkan teman - teman buat file baru dengan nama Textarea.jsx yang terletak didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

Textarea.jsx
import React from 'react'
export default function Textarea({label, className, errors,...props}) {
    return (
        <div className='flex flex-col gap-2'>
            <label className='text-gray-600 dark:text-gray-500 text-sm'>
                {label}
            </label>
            <textarea
                className={`w-full px-4 py-2 border text-sm rounded-md focus:outline-none focus:ring-0 bg-white text-gray-700 focus:border-gray-200 border-gray-200 ${className}`}
                {...props}
            />
            {errors && (
                <small className='text-xs text-red-500'>{errors}</small>
            )}
        </div>
    )
}

Pada kode diatas, pertama kita lakukan import React terlebih dahulu.

Textarea.jsx
import React from 'react'

Selanjutnya kita membuat sebuah React Functional Component dengan beberapa props diantara-nya label, className, errors, dan ...props.

Textarea.jsx
export default function Textarea({label, className, errors, ...props}) 

Component Search

Silahkan teman - teman buat file baru dengan nama Search.jsx yang terletak didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

Search.jsx
import { useForm } from '@inertiajs/react';
import { IconSearch } from '@tabler/icons-react';
import React from 'react'
export default function Search({url, placeholder}) {

    // define use form inertia
    const {data, setData, get} = useForm({
        search : '',
    })

    // define method searchData
    const handleSearchData = (e) => {
        e.preventDefault();

        get(`${url}?search=${data.search}`)
    }

    return (
        <form onSubmit={handleSearchData}>
            <div className='relative'>
                <input
                    type='text'
                    value={data.search}
                    onChange={e => setData('search', e.target.value)}
                    className='py-2 px-4 pr-11 block w-full rounded-lg text-sm border focus:outline-none focus:ring-0 focus:ring-gray-400 text-gray-700 bg-white border-gray-200 focus:border-gray-200'
                    placeholder={placeholder}/>
                <div className='absolute inset-y-0 right-0 flex items-center pointer-events-none pr-4'>
                    <IconSearch size={18} strokeWidth={1.5}/>
                </div>
            </div>
        </form>
    )
}

Pada kode diatas, pertama kita import semua yang kita butuhkan.

Search.jsx
import { useForm } from '@inertiajs/react';
import { IconSearch } from '@tabler/icons-react';
import React from 'react'

Selanjutnya kita membuat sebuah React Functional Component dengan beberapa props diantara-nya label, className, errors, dan ...props.

Search.jsx
export default function Textarea({label, className, errors, ...props}) 

Didalam react function component, kita membuat sebuah state menggunakan form helper yang telah disediakan oleh inerita.

Search.jsx
const {data, setData, get} = useForm({
    search : '',
})

Kemudian kita juga membuat sebuah method baru dengan nama handleSearchData, method ini kita gunakan untuk melakukan pencarian data, dan method ini dijalankan ketika form di submit.

Search.jsx
// define method searchData
const handleSearchData = (e) => {
    e.preventDefault();

    get(`${url}?search=${data.search}`)
}

Component PostCard

Silahkan teman - teman buat file baru dengan nama PostCard.jsx yang terletak didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

PostCard.jsx
import React from 'react'

export default function PostCard({ post }) {
  return (
    <div className='p-8 bg-white rounded-xl border'>
        <div className='font-semibold line-clamp-1'>{post.title}</div>
        <div className='text-sm text-gray-500 line-clamp-2'>{post.content}</div>
        <div className='text-sm font-semibold text-sky-500 mt-4'>
            Posted by - <span className='underline'>{post.user.name}</span>
        </div>
    </div>
  )
}

Pada kode diatas, pertama kita lakukan import React terlebih dahulu.

PostCard.jsx
import React from 'react'

Selanjutnya kita membuat sebuah React Functional Component dengan sebuah props post.

PostCard.jsx
export default function PostCard({ post }) 

Component Pagination

Silahkan teman - teman buat file baru dengan nama Pagination.jsx yang terletak didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

Pagination.jsx
import React from 'react'
import { Link } from '@inertiajs/react';
import { IconChevronRight, IconChevronLeft } from '@tabler/icons-react';
export default function Pagination({ links }) {

    const style = 'p-1 text-sm border rounded-md bg-white text-gray-500 hover:bg-gray-100'

    return (
        <>
            <ul className="mt-2 lg:mt-5 justify-end flex items-center gap-1">
                {links.map((item, i) => {
                    return item.url != null ? (
                        item.label.includes('Previous') ? (
                            <Link className={style} key={i} href={item.url}>
                                <IconChevronLeft size={'20'} strokeWidth={'1.5'}/>
                            </Link>
                        ) : item.label.includes('Next') ? (
                            <Link className={style} key={i} href={item.url}>
                                <IconChevronRight size={'20'} strokeWidth={'1.5'}/>
                            </Link>
                        ) : (
                            <Link className={`px-2 py-1 text-sm border  rounded-md text-gray-500 hover:bg-gray-100 ${item.active ? 'bg-white text-gray-700' : 'bg-white'}`} key={i} href={item.url}>
                                {item.label}
                            </Link>
                        )
                    ) : null;
                })}
            </ul>
        </>
    )
}

Pada kode diatas, pertama kita import semua yang kita butuhkan.

Pagination.jsx
import React from 'react'
import { Link } from '@inertiajs/react';
import { IconChevronRight, IconChevronLeft } from '@tabler/icons-react';

Selanjutnya kita membuat sebuah React Functional Component dengan sebuah props links.

Pagination.jsx
export default function Pagination({ links })

Component Button

Silahkan teman - teman buat file baru dengan nama Button.jsx yang terletak didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

Button.jsx
import { Link, useForm } from '@inertiajs/react'
import { IconArrowBack, IconCheck, IconPencilCog, IconPlus, IconTrash } from '@tabler/icons-react';
import React from 'react'
import Swal from 'sweetalert2';
export default function Button({ type, url, className, children, ...props }) {

    const { delete : destroy } = useForm();

    const handleDeleteData = async (url) => {
        Swal.fire({
            title: 'Are you sure you want to delete this?',
            text: 'Data is unrecoverable!',
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: 'Yes, delete it!',
            cancelButtonText: 'Cancel'
        }).then((result) => {
            if (result.isConfirmed) {
                destroy(url)

                Swal.fire({
                    title: 'Success!',
                    text: 'Data deleted successfully!',
                    icon: 'success',
                    showConfirmButton: false,
                    timer: 1500
                })
            }
        })
    }

    return (
        <>
            {type === 'add' &&
                <Link href={url} className='px-4 py-2 text-sm border rounded-lg bg-white text-gray-700 flex items-center gap-2 hover:bg-gray-100'>
                    <IconPlus size={18} strokeWidth={1.5}/> <span className='hidden lg:flex'>Create New Data</span>
                </Link>
            }
            {type === 'modal' &&
                <button {...props} type='button' className={`${className} px-4 py-2 text-sm border rounded-lg flex items-center gap-2`}>
                    {children}
                </button>
            }
            {type === 'submit' &&
                <button type='submit' className='px-4 py-2 text-sm rounded-lg border border-teal-100 bg-teal-50 text-teal-500 flex items-center gap-2 hover:bg-teal-100'>
                    <IconCheck size={16} strokeWidth={1.5}/> Save Data
                </button>
            }
            {type === 'cancel' &&
                <Link href={url} className='px-4 py-2 text-sm rounded-lg border border-rose-100 bg-rose-50 text-rose-500 flex items-center gap-2 hover:bg-rose-100'>
                    <IconArrowBack size={16} strokeWidth={1.5}/> Go Back
                </Link>
            }
            {type === 'edit' &&
                <Link href={url} className='px-4 py-2 rounded-lg bg-orange-50 text-orange-500 flex items-center gap-2 hover:bg-orange-100'>
                    <IconPencilCog size={16} strokeWidth={1.5}/>
                </Link>
            }
            {type === 'delete' &&
                <button onClick={() => handleDeleteData(url)} className='px-4 py-2 rounded-lg bg-rose-50 text-rose-500 flex items-center gap-2 hover:bg-rose-100'>
                    <IconTrash size={18} strokeWidth={1.5}/>
                </button>
            }
        </>
    )
}

Pada kode diatas, pertama kita import semua yang kita butuhkan.

Button.jsx
import { Link, useForm } from '@inertiajs/react'
import { IconArrowBack, IconCheck, IconPencilCog, IconPlus, IconTrash } from '@tabler/icons-react';
import React from 'react'
import Swal from 'sweetalert2';

Selanjutnya kita membuat sebuah React Functional Component dengan beberapa props diantara-nya type, url, className, children, dan ...props.

Button.jsx
export default function Button({ type, url, className, children, ...props }) 

Didalam react function component, kita membuat sebuah state menggunakan form helper yang telah disediakan oleh inerita.

Button.jsx
const { delete : destroy } = useForm();

Kemudian kita juga membuat sebuah method baru dengan nama handleDeleteData dan method ini kita tambahkan sebuah paramater url yang diambil dari props url yang kita kirimkan.

Button.jsx
const handleDeleteData = async (url) => {
    Swal.fire({
        title: 'Are you sure you want to delete this?',
        text: 'Data is unrecoverable!',
        icon: 'warning',
        showCancelButton: true,
        confirmButtonColor: '#3085d6',
        cancelButtonColor: '#d33',
        confirmButtonText: 'Yes, delete it!',
        cancelButtonText: 'Cancel'
    }).then((result) => {
        if (result.isConfirmed) {
            destroy(url)

            Swal.fire({
                title: 'Success!',
                text: 'Data deleted successfully!',
                icon: 'success',
                showConfirmButton: false,
                timer: 1500
            })
        }
    })
}

Didalam method tersebut kita menggunakan SweetAlert untuk menampilkan jendela konfirmasi sebelum data benar-benar dihapus.

Button.jsx
Swal.fire({
    title: 'Are you sure you want to delete this?',
    text: 'Data is unrecoverable!',
    icon: 'warning',
    showCancelButton: true,
    confirmButtonColor: '#3085d6',
    cancelButtonColor: '#d33',
    confirmButtonText: 'Yes, delete it!',
    cancelButtonText: 'Cancel'
})

Ketika jendela konfirmasi terbuka dan button Yes, delete it! diklik. maka kita akan melakukan proses penghapusan data ke dalam database menggunakan form helper yang disediakan oleh inertia.

Button.jsx
destroy(url)

Method handleDeleteData akan dijalankan ketika button diklik.

Button.jsx
{type === 'delete' &&
    <button onClick={() => handleDeleteData(url)} className='px-4 py-2 rounded-lg bg-rose-50 text-rose-500 flex items-center gap-2 hover:bg-rose-100'>
        <IconTrash size={18} strokeWidth={1.5}/>
    </button>
}

Pada component button, kita juga membuat beberapa pengkondisian sesuai dengan props type yang dikirimkan.

Button.jsx
{type === 'add' &&
    <Link href={url} className='px-4 py-2 text-sm border rounded-lg bg-white text-gray-700 flex items-center gap-2 hover:bg-gray-100'>
        <IconPlus size={18} strokeWidth={1.5}/> <span className='hidden lg:flex'>Create New Data</span>
    </Link>
}
{type === 'modal' &&
    <button {...props} type='button' className={`${className} px-4 py-2 text-sm border rounded-lg flex items-center gap-2`}>
        {children}
    </button>
}
{type === 'submit' &&
    <button type='submit' className='px-4 py-2 text-sm rounded-lg border border-teal-100 bg-teal-50 text-teal-500 flex items-center gap-2 hover:bg-teal-100'>
        <IconCheck size={16} strokeWidth={1.5}/> Save Data
    </button>
}
{type === 'cancel' &&
    <Link href={url} className='px-4 py-2 text-sm rounded-lg border border-rose-100 bg-rose-50 text-rose-500 flex items-center gap-2 hover:bg-rose-100'>
        <IconArrowBack size={16} strokeWidth={1.5}/> Go Back
    </Link>
}
{type === 'edit' &&
    <Link href={url} className='px-4 py-2 rounded-lg bg-orange-50 text-orange-500 flex items-center gap-2 hover:bg-orange-100'>
        <IconPencilCog size={16} strokeWidth={1.5}/>
    </Link>
}
{type === 'delete' &&
    <button onClick={() => handleDeleteData(url)} className='px-4 py-2 rounded-lg bg-rose-50 text-rose-500 flex items-center gap-2 hover:bg-rose-100'>
        <IconTrash size={18} strokeWidth={1.5}/>
    </button>
}

Component Table

Silahkan teman - teman buat file baru dengan nama Table.jsx yang terletak didalam folder app/resources/js/Components, kemudian masukan kode berikut ini :

Table.jsx
import React from 'react'

const Card = ({ title, className, children }) => {
    return (
        <>
            <div className={`p-4 rounded-t-lg border ${className} bg-white`}>
                <div className='flex items-center gap-2 font-semibold text-sm text-gray-700 uppercase'>
                    {title}
                </div>
            </div>
            <div className='bg-white rounded-b-lg border-t-0'>
                {children}
            </div>
        </>

    )
}

const Table = ({ children }) => {
    return (
        <div className="w-full overflow-hidden overflow-x-auto border-collapse rounded-b-lg border border-t-0">
            <table className="w-full text-sm">
                {children}
            </table>
        </div>
    );
};

const Thead = ({ className, children }) => {
    return (
        <thead className={`${className} border-b bg-gray-50`}>{children}</thead>
    );
};

const Tbody = ({ className, children }) => {
    return (
        <tbody className={`${className} divide-y bg-white`}>
            {children}
        </tbody>
    );
};

const Td = ({ className, children}) => {
    return (
        <td
            className={`${className} whitespace-nowrap p-4 align-middle text-gray-700`}
        >
            {children}
        </td>
    );
};

const Th = ({ className, children }) => {
    return (
        <th
            scope="col"
            className={`${className} h-12 px-4 text-left align-middle font-medium text-gray-700`}
        >
            {children}
        </th>
    );
};

const Empty = ({colSpan, message, children}) => {
    return (
        <tr>
            <td colSpan={colSpan}>
                <div className="flex items-center justify-center h-96">
                    <div className="text-center">
                        {children}
                        <div className="mt-5">
                            {message}
                        </div>
                    </div>
                </div>
            </td>
        </tr>
    )
}

Table.Card = Card;
Table.Thead = Thead;
Table.Tbody = Tbody;
Table.Td = Td;
Table.Th = Th;
Table.Empty = Empty;

export default Table;

Pada kode diatas, kita membuat sebuah component table menjadi beberapa sub-component yang lebih modular seperti berikut ini :

Subcomponent Card

Sub-component ini digunakan untuk membungkus table dengan header dan isi yang terpisah, dengan props title, className dan children.

Table.jsx
const Card = ({ title, className, children }) => {
    return (
        <>
            <div className={`p-4 rounded-t-lg border ${className} bg-white`}>
                <div className='flex items-center gap-2 font-semibold text-sm text-gray-700 uppercase'>
                    {title}
                </div>
            </div>
            <div className='bg-white rounded-b-lg border-t-0'>
                {children}
            </div>
        </>
    );
};

Subcomponent Table

Sub-component ini digunakan untuk menampilkan table dengan props children.

Table.jsx
const Table = ({ children }) => {
    return (
        <div className="w-full overflow-hidden overflow-x-auto border-collapse rounded-b-lg border border-t-0">
            <table className="w-full text-sm">
                {children}
            </table>
        </div>
    );
};

Subcomponent Thead dan Tbody

Sub-component ini digunakan untuk membungkus bagian header dan body tabel dengan props className dan children.

Table.jsx
const Thead = ({ className, children }) => {
    return (
        <thead className={`${className} border-b bg-gray-50`}>{children}</thead>
    );
};

const Tbody = ({ className, children }) => {
    return (
        <tbody className={`${className} divide-y bg-white`}>
            {children}
        </tbody>
    );
};

Subcomponent Th dan Td

Sub-component ini digunakan untuk elemen kolom header dan data dengan props className dan children.

Table.jsx
const Th = ({ className, children }) => {
    return (
        <th
            scope="col"
            className={`${className} h-12 px-4 text-left align-middle font-medium text-gray-700`}
        >
            {children}
        </th>
    );
};

const Td = ({ className, children }) => {
    return (
        <td
            className={`${className} whitespace-nowrap p-4 align-middle text-gray-700`}
        >
            {children}
        </td>
    );
};

Subcomponent Empty

Sub-component ini digunakan untuk menampilkan pesan jika data didalam tabel kosong dengan props colspan, message dan children.

Table.jsx
const Empty = ({ colSpan, message, children }) => {
    return (
        <tr>
            <td colSpan={colSpan}>
                <div className="flex items-center justify-center h-96">
                    <div className="text-center">
                        {children}
                        <div className="mt-5">{message}</div>
                    </div>
                </div>
            </td>
        </tr>
    );
};

Kemudian semua semua sub-component diatas kita jadikan sebuah properti Table agar penggunaannya lebih rapi dengan cara kita export sebagai Table.

Table.jsx
Table.Card = Card;
Table.Thead = Thead;
Table.Tbody = Tbody;
Table.Td = Td;
Table.Th = Th;
Table.Empty = Empty;

export default Table;

Penutup

Setelah berhasil membuat beberapa component yang akan kita gunakan nantinya, kita akan lanjutkan untuk pembuatan sebuah utils untuk melakukan pembatasan akses sesuai dengan roles & permissions yang dimiliki oleh user.

Artikel Lainnya

Beberapa artikel rekomendasi lainnya untuk menambah pengetahuan.

JurnalKoding

Mulai asah skill dengan berbagai macam teknologi - teknologi terbaru seperti Laravel, React, Vue, Inertia, Tailwind CSS, dan masih banyak lagi.

© 2024 JurnalKoding, Inc. All rights reserved.