Tipado Generik dalam Aksi: Implementasi Tabel Fleksibel di React dengan TypeScript
React dan TypeScript adalah kekuatan dinamis dalam pengembangan web modern. TypeScript membawa keamanan tipe yang sangat dibutuhkan ke dunia JavaScript, membantu kita menangkap kesalahan sebelum dijalankan. React, di sisi lain, menyediakan cara deklaratif dan berbasis komponen untuk membangun antarmuka pengguna yang interaktif. Ketika keduanya digabungkan, kita dapat membangun aplikasi yang kuat, mudah dipelihara, dan aman dari kesalahan.
Salah satu pola umum dalam pengembangan web adalah menampilkan data tabular. Tabel adalah cara yang efisien dan mudah dibaca untuk menyajikan informasi terstruktur kepada pengguna. Dalam artikel ini, kita akan menjelajahi bagaimana memanfaatkan tipado generik TypeScript untuk membuat komponen tabel React yang sangat fleksibel dan dapat digunakan kembali.
Mengapa Menggunakan Tipado Generik untuk Komponen Tabel?
Sebelum kita menyelami kode, mari kita bahas mengapa tipado generik sangat bermanfaat dalam konteks komponen tabel:
- Keamanan Tipe: Generik memungkinkan kita untuk mendefinisikan tipe data yang digunakan oleh komponen tabel kita secara dinamis. Ini berarti bahwa kita dapat memastikan bahwa data yang kita tampilkan sesuai dengan struktur yang kita harapkan, mencegah kesalahan runtime.
- Penggunaan Ulang: Dengan generik, kita dapat membuat satu komponen tabel yang dapat digunakan untuk menampilkan berbagai jenis data. Kita tidak perlu menulis komponen tabel terpisah untuk setiap jenis data yang berbeda.
- Fleksibilitas: Generik memberi kita fleksibilitas untuk menyesuaikan komponen tabel kita sesuai dengan kebutuhan spesifik kita. Kita dapat menentukan properti dan fungsi yang berbeda untuk berbagai jenis data.
- Kode yang Lebih Bersih: Menggunakan generik dapat menghasilkan kode yang lebih bersih dan mudah dibaca. Ini karena kita tidak perlu menggunakan tipe
any
atau melakukan casting tipe secara manual.
Kerangka Artikel: Dari Konsep Hingga Implementasi
Berikut adalah kerangka yang akan kita ikuti untuk membangun komponen tabel generik kita:
- Definisikan Tipe Data: Tentukan interface TypeScript yang mewakili struktur data yang akan kita tampilkan dalam tabel.
- Buat Komponen Tabel Dasar: Bangun komponen React dasar yang menerima data dan konfigurasi kolom sebagai properti.
- Implementasikan Tipado Generik: Gunakan generik TypeScript untuk membuat komponen tabel lebih fleksibel dan aman tipe.
- Tambahkan Konfigurasi Kolom: Izinkan pengembang untuk mengkonfigurasi kolom yang akan ditampilkan, termasuk judul, bidang data, dan format tampilan.
- Implementasikan Sortir: Tambahkan fungsionalitas untuk mengurutkan data berdasarkan kolom yang berbeda.
- Implementasikan Pencarian: Tambahkan fungsionalitas untuk mencari data dalam tabel.
- Penomoran Halaman: Implementasikan penomoran halaman untuk menangani set data yang besar.
- Styling: Terapkan gaya dasar pada tabel untuk membuatnya terlihat lebih menarik.
- Contoh Penggunaan: Berikan contoh konkret bagaimana menggunakan komponen tabel dengan data yang berbeda.
- Kesimpulan: Rangkum manfaat menggunakan tipado generik untuk komponen tabel React dan berikan saran untuk pengembangan lebih lanjut.
1. Definisikan Tipe Data
Langkah pertama adalah mendefinisikan interface TypeScript yang mewakili struktur data yang akan kita tampilkan dalam tabel. Misalnya, jika kita ingin menampilkan daftar pengguna, kita dapat mendefinisikan interface seperti ini:
“`typescript
interface User {
id: number;
firstName: string;
lastName: string;
email: string;
age: number;
}
“`
Interface ini mendefinisikan tipe data untuk setiap properti dalam objek User
. Kita akan menggunakan interface ini untuk menipe data yang kita tampilkan dalam tabel.
2. Buat Komponen Tabel Dasar
Selanjutnya, kita akan membuat komponen React dasar yang menerima data dan konfigurasi kolom sebagai properti. Komponen ini akan bertanggung jawab untuk merender tabel HTML.
“`typescript
import React from ‘react’;
interface Props {
data: any[];
columns: {
Header: string;
accessor: string;
}[];
}
const BasicTable: React.FC
return (
{column.Header} |
---|
{row[column.accessor]} |
);
};
export default BasicTable;
“`
Komponen BasicTable
menerima dua properti:
data
: Array data yang akan ditampilkan dalam tabel. Saat ini bertipeany[]
, yang akan kita ubah nanti.columns
: Array objek yang mendefinisikan kolom yang akan ditampilkan dalam tabel. Setiap objek kolom memiliki propertiHeader
(judul kolom) danaccessor
(bidang data yang akan ditampilkan).
Komponen ini merender tabel HTML dasar dengan header dan baris data. Header dibuat menggunakan properti Header
dari setiap objek kolom, dan data sel diisi menggunakan properti accessor
.
3. Implementasikan Tipado Generik
Sekarang kita akan mengimplementasikan tipado generik untuk membuat komponen tabel lebih fleksibel dan aman tipe. Kita akan mengubah tipe data data
dari any[]
menjadi tipe generik T[]
, di mana T
adalah tipe data yang akan kita tampilkan dalam tabel.
“`typescript
import React from ‘react’;
interface Props
data: T[];
columns: {
Header: string;
accessor: keyof T; // Memastikan accessor sesuai dengan key pada T
}[];
}
const GenericTable =
return (
{column.Header} |
---|
{String(row[column.accessor])} |
);
};
export default GenericTable;
“`
Beberapa perubahan penting telah dilakukan:
- Tipe Generik
T
: Kita telah memperkenalkan tipe generikT
untuk mewakili tipe data yang akan kita tampilkan dalam tabel. Props
: InterfaceProps
sekarang menggunakan tipe generikT
untuk mendefinisikan tipe data untuk propertidata
.data
sekarang bertipeT[]
, yang berarti array dari tipeT
.keyof T
untukaccessor
: Kita telah mengubah tipeaccessor
menjadikeyof T
. Ini memastikan bahwa nilaiaccessor
harus berupa kunci yang valid pada tipeT
. Ini meningkatkan keamanan tipe dan mencegah kesalahan di mana kita mencoba mengakses bidang yang tidak ada.- Batasan Tipe
T extends {}
: Kita telah menambahkan batasanT extends {}
. Ini memastikan bahwaT
adalah tipe objek. - Konversi ke String: Kita melakukan konversi explicit menjadi string dengan `String(row[column.accessor])` untuk mengatasi kemungkinan error dari penggunaan tipe data non-string.
Dengan perubahan ini, komponen GenericTable
kita sekarang menggunakan tipado generik. Ini berarti bahwa kita dapat menggunakan komponen yang sama untuk menampilkan berbagai jenis data tanpa harus menulis komponen terpisah untuk setiap jenis data.
4. Tambahkan Konfigurasi Kolom yang Lebih Fleksibel
Sekarang mari tingkatkan konfigurasi kolom untuk memberikan lebih banyak fleksibilitas. Kita akan memungkinkan pengembang untuk menentukan format tampilan untuk setiap kolom.
“`typescript
import React from ‘react’;
interface ColumnConfig
Header: string;
accessor: keyof T;
Cell?: (row: T) => React.ReactNode; // Opsi untuk memformat tampilan sel
}
interface Props
data: T[];
columns: ColumnConfig
}
const FlexibleTable =
return (
{column.Header} |
---|
{column.Cell ? column.Cell(row) : String(row[column.accessor])} |
);
};
export default FlexibleTable;
“`
Kita telah menambahkan properti Cell
opsional ke interface ColumnConfig
. Properti Cell
adalah fungsi yang menerima objek data baris sebagai argumen dan mengembalikan node React yang akan dirender di sel. Jika properti Cell
tidak ditentukan, kita akan menampilkan nilai data mentah, seperti sebelumnya.
Berikut adalah contoh bagaimana kita dapat menggunakan properti Cell
untuk memformat tampilan kolom usia:
“`typescript
const columns: ColumnConfig
{
Header: ‘ID’,
accessor: ‘id’,
},
{
Header: ‘Nama Depan’,
accessor: ‘firstName’,
},
{
Header: ‘Nama Belakang’,
accessor: ‘lastName’,
},
{
Header: ‘Email’,
accessor: ’email’,
},
{
Header: ‘Usia’,
accessor: ‘age’,
Cell: (user) => {user.age} tahun, // Memformat usia
},
];
“`
Dalam contoh ini, kita telah menentukan fungsi Cell
untuk kolom usia. Fungsi ini mengembalikan elemen span
yang menampilkan usia diikuti oleh teks “tahun”.
5. Implementasikan Sortir
Menambahkan fungsionalitas sortir ke komponen tabel kita akan membuatnya lebih interaktif dan berguna. Kita akan memungkinkan pengguna untuk mengurutkan data berdasarkan kolom yang berbeda dengan mengklik header kolom.
“`typescript
import React, { useState, useCallback } from ‘react’;
interface ColumnConfig
Header: string;
accessor: keyof T;
Cell?: (row: T) => React.ReactNode;
sortable?: boolean; // Menambahkan properti sortable
}
interface Props
data: T[];
columns: ColumnConfig
}
const SortableTable =
const [sortBy, setSortBy] = useState
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(‘asc’);
const handleSort = useCallback(
(accessor: keyof T) => {
if (accessor === sortBy) {
setSortDirection(sortDirection === ‘asc’ ? ‘desc’ : ‘asc’);
} else {
setSortBy(accessor);
setSortDirection(‘asc’);
}
},
[sortBy, sortDirection]
);
const sortedData = React.useMemo(() => {
if (!sortBy) {
return data;
}
const column = columns.find((col) => col.accessor === sortBy);
if (!column || !column.sortable) {
return data;
}
const sorted = […data].sort((a, b) => {
const valueA = a[sortBy];
const valueB = b[sortBy];
if (valueA === undefined || valueA === null) return -1;
if (valueB === undefined || valueB === null) return 1;
if (typeof valueA === ‘string’ && typeof valueB === ‘string’) {
return sortDirection === ‘asc’ ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
}
if (typeof valueA === ‘number’ && typeof valueB === ‘number’) {
return sortDirection === ‘asc’ ? valueA – valueB : valueB – valueA;
}
return 0;
});
return sorted;
}, [data, sortBy, sortDirection, columns]);
return (
column.sortable ? handleSort(column.accessor) : undefined} style={{ cursor: column.sortable ? ‘pointer’ : ‘default’ }} > {column.Header} {sortBy === column.accessor && ( {sortDirection === ‘asc’ ? ‘ ▲’ : ‘ ▼’} )} |
---|
{column.Cell ? column.Cell(row) : String(row[column.accessor])} |
);
};
export default SortableTable;
“`
Beberapa perubahan penting telah dilakukan:
sortable
Properti: Kita telah menambahkan propertisortable
opsional ke interfaceColumnConfig
. Properti ini menentukan apakah kolom dapat diurutkan.- State
sortBy
dansortDirection
: Kita telah menambahkan statesortBy
dansortDirection
untuk melacak kolom mana yang sedang diurutkan dan arah pengurutan. handleSort
Fungsi: Kita telah menambahkan fungsihandleSort
yang dipanggil ketika header kolom diklik. Fungsi ini memperbarui statesortBy
dansortDirection
.sortedData
Variabel: Kita telah menambahkan variabelsortedData
yang menyimpan data yang telah diurutkan. Variabel ini menggunakanuseMemo
untuk menghitung data yang diurutkan hanya ketika data, sortBy, atau sortDirection berubah.- Pengaturan Tampilan: Tampilan diubah untuk menunjukkan arah pengurutan pada header yang diklik.
Sekarang, ketika pengguna mengklik header kolom, data akan diurutkan berdasarkan kolom itu. Jika pengguna mengklik header kolom lagi, arah pengurutan akan dibalik.
6. Implementasikan Pencarian
Selanjutnya, mari kita tambahkan fungsionalitas pencarian ke komponen tabel kita. Kita akan memungkinkan pengguna untuk mencari data dalam tabel dengan memasukkan istilah pencarian ke dalam input pencarian.
“`typescript
import React, { useState, useCallback } from ‘react’;
interface ColumnConfig
Header: string;
accessor: keyof T;
Cell?: (row: T) => React.ReactNode;
sortable?: boolean;
}
interface Props
data: T[];
columns: ColumnConfig
}
const SearchableTable =
const [searchTerm, setSearchTerm] = useState(”);
const handleSearch = (event: React.ChangeEvent
setSearchTerm(event.target.value);
};
const filteredData = React.useMemo(() => {
const lowerSearchTerm = searchTerm.toLowerCase();
return data.filter(row => {
return columns.some(column => {
const value = String(row[column.accessor]).toLowerCase();
return value.includes(lowerSearchTerm);
});
});
}, [data, searchTerm, columns]);
return (
);
};
export default SearchableTable;
“`
Beberapa perubahan penting telah dilakukan:
- State
searchTerm
: Kita telah menambahkan statesearchTerm
untuk melacak istilah pencarian yang dimasukkan oleh pengguna. handleSearch
Fungsi: Kita telah menambahkan fungsihandleSearch
yang dipanggil ketika pengguna mengubah nilai input pencarian. Fungsi ini memperbarui statesearchTerm
.filteredData
Variabel: Kita telah menambahkan variabelfilteredData
yang menyimpan data yang telah difilter. Variabel ini menggunakanuseMemo
untuk menghitung data yang difilter hanya ketika data atau searchTerm berubah.
Sekarang, ketika pengguna memasukkan istilah pencarian ke dalam input pencarian, tabel akan difilter untuk hanya menampilkan baris yang cocok dengan istilah pencarian.
7. Penomoran Halaman
Untuk set data yang besar, penomoran halaman sangat penting untuk meningkatkan kinerja dan pengalaman pengguna. Kita akan mengimplementasikan penomoran halaman untuk membagi data menjadi halaman yang lebih kecil.
“`typescript
import React, { useState, useMemo } from ‘react’;
interface ColumnConfig
Header: string;
accessor: keyof T;
Cell?: (row: T) => React.ReactNode;
sortable?: boolean;
}
interface Props
data: T[];
columns: ColumnConfig
pageSize?: number; // Jumlah baris per halaman
}
const PaginatedTable =
const [currentPage, setCurrentPage] = useState(1);
const totalPages = useMemo(() => Math.ceil(data.length / pageSize), [data.length, pageSize]);
const paginatedData = useMemo(() => {
const startIndex = (currentPage – 1) * pageSize;
const endIndex = startIndex + pageSize;
return data.slice(startIndex, endIndex);
}, [data, currentPage, pageSize]);
const handlePageChange = (newPage: number) => {
setCurrentPage(newPage);
};
return (
Halaman {currentPage} dari {totalPages}
);
};
export default PaginatedTable;
“`
Beberapa perubahan penting telah dilakukan:
pageSize
Properti: Kita telah menambahkan propertipageSize
opsional ke interfaceProps
. Properti ini menentukan jumlah baris yang akan ditampilkan per halaman. Jika propertipageSize
tidak ditentukan, defaultnya adalah 10.- State
currentPage
: Kita telah menambahkan statecurrentPage
untuk melacak halaman mana yang sedang ditampilkan. totalPages
Variabel: Kita telah menambahkan variabeltotalPages
yang menghitung jumlah total halaman.paginatedData
Variabel: Kita telah menambahkan variabelpaginatedData
yang menyimpan data yang akan ditampilkan pada halaman saat ini. Variabel ini menggunakanuseMemo
untuk menghitung data yang dipaginasi hanya ketika data, currentPage, atau pageSize berubah.handlePageChange
Fungsi: Kita telah menambahkan fungsihandlePageChange
yang dipanggil ketika pengguna mengklik tombol “Sebelumnya” atau “Selanjutnya”. Fungsi ini memperbarui statecurrentPage
.
Sekarang, tabel akan menampilkan hanya sejumlah baris per halaman yang ditentukan oleh properti pageSize
. Pengguna dapat menavigasi antar halaman dengan mengklik tombol “Sebelumnya” dan “Selanjutnya”.
8. Styling
Styling adalah aspek penting dalam membuat tabel yang menarik dan mudah dibaca. Kita dapat menggunakan CSS atau pustaka styling seperti Styled Components atau Material UI untuk menata tabel kita.
Berikut adalah contoh styling dasar menggunakan CSS biasa:
“`css
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 8px;
border: 1px solid #ddd;
text-align: left;
}
th {
background-color: #f2f2f2;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
“`
Ini adalah contoh styling dasar yang dapat Anda sesuaikan sesuai dengan preferensi Anda.
9. Contoh Penggunaan
Sekarang mari kita lihat bagaimana kita dapat menggunakan komponen tabel generik kita dengan data yang berbeda.
“`typescript
import React from ‘react’;
import GenericTable from ‘./GenericTable’;
interface Product {
id: number;
name: string;
price: number;
description: string;
}
const products: Product[] = [
{
id: 1,
name: ‘Laptop’,
price: 1200,
description: ‘Laptop high-end dengan performa tinggi.’,
},
{
id: 2,
name: ‘Mouse’,
price: 25,
description: ‘Mouse ergonomis untuk kenyamanan maksimal.’,
},
{
id: 3,
name: ‘Keyboard’,
price: 75,
description: ‘Keyboard mekanis dengan switch yang responsif.’,
},
];
const productColumns = [
{
Header: ‘ID’,
accessor: ‘id’,
},
{
Header: ‘Nama’,
accessor: ‘name’,
},
{
Header: ‘Harga’,
accessor: ‘price’,
Cell: (product) => ${product.price.toFixed(2)},
},
{
Header: ‘Deskripsi’,
accessor: ‘description’,
},
];
const App = () => {
return (
);
};
export default App;
“`
Dalam contoh ini, kita membuat antarmuka Product
dan array data products
. Kita juga mendefinisikan array productColumns
yang menentukan kolom yang akan ditampilkan dalam tabel. Kita kemudian menggunakan komponen GenericTable
untuk menampilkan data produk.
10. Kesimpulan
Dalam artikel ini, kita telah menjelajahi bagaimana memanfaatkan tipado generik TypeScript untuk membuat komponen tabel React yang sangat fleksibel dan dapat digunakan kembali. Dengan menggunakan generik, kita dapat memastikan keamanan tipe, meningkatkan penggunaan ulang kode, dan mengurangi duplikasi kode.
Beberapa saran untuk pengembangan lebih lanjut:
- Ekspor ke CSV: Tambahkan fungsionalitas untuk mengekspor data tabel ke file CSV.
- Kustomisasi Sel: Berikan lebih banyak opsi untuk menyesuaikan tampilan sel, seperti menambahkan ikon atau tautan.
- Integrasi API: Buat komponen tabel dapat mengambil data dari API secara dinamis.
- Pengujian Unit: Tulis pengujian unit untuk memastikan bahwa komponen tabel berfungsi dengan benar.
Dengan mengikuti langkah-langkah ini, Anda dapat membuat komponen tabel React yang kuat dan fleksibel yang dapat digunakan untuk menampilkan berbagai jenis data dalam aplikasi Anda.
“`