Menggunakan Antd dan React untuk Implement Editable Cells dan Table
We will be building an editable table with antd & reactjs.
Halo developer, disini saya akan sharing bagaimana membuat Editable Cells menggunakan Ant design(antd). Antd dikembangkan oleh Alibaba Group. Ini salah satu framework UI yang cukup populer untuk aplikasi web dan aplikasi berbasis React. Antd menyediakan banyak komponen UI yang dapat digunakan developer saat membangun aplikasi web. Hal ini membantu developer agar UI yang dibuat dapat konsisten dan menarik. Kita dapat mempelajari dokumentasinya di situs resmi ant.design.
Prerequisite
Terdapat persyaratan yang diperlukan untuk membuat aplikasi ini.
- ReactJS (disini saya menggunakan versi 18.2.0)
- Antd (saat membuat tulisan ini, saya menggunakan versi 4)
Mendifinisikan Data Dummy
Kode dibawah merupakan contoh data yang akan ditampilkan
const data = {
content: [
{
id: 1,
name: 'John Sr',
age: 60,
address: 'Sudirman No 1, Jakarta Selatan',
},
{
id: 2,
name: 'Lennon Jr',
age: 32,
address: 'Kemanggisan No 3, Jakarta Barat',
},
]
}
Mendifinisikan Column
Setelah mengetahui response data yang akan ditampilkan, selanjutnya kita perlu definisikan kolom sesuai dengan parameter data tersebut.
const columns = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Age',
dataIndex: 'age',
},
{
title: 'Address',
dataIndex: 'address',
},
]
Implementasi EditableCell dan EditableContext
Selanjutnya kita dapat memanfaatkan beberapa hooks di ReactJs pada file Context.tsx
.
// src/components/home/table/Context.tsx
import { createContext, useContext, useEffect, useRef, useState } from 'react';
import { validateInput } from '../../../utils/validate';
import { Form, Input } from 'antd';
export const EditableContext: any = createContext(null);
export const EditableCell = (props: any) => {
const {
title,
editable,
children,
dataIndex,
record,
index,
suffix,
validate,
handleSave,
success,
setSuccess,
handleValidate,
...restProps
} = props
const [editing, setEditing] = useState(false);
const inputRef: any = useRef(null);
const form: any = useContext(EditableContext);
const content = restProps?.data?.content || [];
useEffect(() => {
if (editing) {
inputRef.current.focus();
}
}, [editing]);
const toggleEdit = (val: any) => {
setEditing(!editing);
const value = val.target.value;
form.setFieldsValue({
[dataIndex]: value,
});
};
const save = async (e: any) => {
try {
toggleEdit(e);
if (e.target.value) {
const val = await form.validateFields();
await handleSave({
...record,
...val,
});
}
const suc = success;
const bool = await handleValidate(
index,
record,
dataIndex,
e.target.value
);
if (index !== undefined) {
suc[index] = bool;
setSuccess(suc);
}
} catch (errInfo) {
//
}
};
let errorMsg = null;
let validateStatus: '' | 'success' | 'warning' | 'error' | 'validating' = '';
if (
validate &&
validate.length &&
validate[index] &&
validate[index]?.[dataIndex]?.errorMsg
) {
errorMsg = validate[index]?.[dataIndex]?.errorMsg;
validateStatus = validate[index]?.[dataIndex]?.validateStatus;
}
let childNode = children;
const value =
children && Array.isArray(children) && children.length
? children[children.length - 1]
: '';
if (editable) {
childNode = editing ? (
<Form.Item
style={{
margin: 0,
}}
name={dataIndex}
rules={[
{
required: true,
message: `${title} is required.`,
},
]}
>
<Input
style={{
width: '100%',
}}
max={suffix === 'th' ? 100 : undefined}
placeholder="0"
suffix={suffix}
ref={inputRef}
disabled={restProps?.disabled}
onBlur={save}
onKeyDown={(e) =>
validateInput(e, () => {}, {
minLength: 1,
maxLength: suffix === 'th' ? 2 : undefined,
allowDecimal: suffix === 'th',
isNumber: true,
})
}
/>
</Form.Item>
) : (
<Form.Item
style={{
margin: 0,
}}
help={errorMsg}
validateStatus={validateStatus}
>
<Input
className="editable-cell-value-wrap"
style={{
paddingRight: 24,
}}
onFocus={toggleEdit}
value={value}
disabled={restProps?.isDisabled}
/>
</Form.Item>
);
}
if (dataIndex === 'rangeOmzetUpper' && index === content.length - 1) {
childNode = <></>;
}
return <td {...restProps}>{childNode}</td>;
};
Terdapat 5 hooks yang saya gunakan untuk membuat aplikasi ini, yaitu:
createContext
digunakan untuk membuat objek konteks yang memungkinkan komponen-komponen dalam aplikasi React berbagi data tanpa perlu melewati prop dari komponen ke komponen. Pada kode diatas, saya menggunakannya pada variableEditableContext
.useContext
digunakan untuk mengakses data yang disediakan oleh objek konteks yang dibuat dengancreateContext
. Ini memungkinkan komponen untuk mengambil nilai dari konteks. Saya menempatkannya pada variableform
untuk mengakses nilai dariEditableContext.
useEffect
digunakan untuk menangani side effect dalam komponen React. Pada contoh saya, digunakan untuk mengetahui perubahan di variable editing.useRef
digunakan untuk membuat referensi ke elemen DOM atau nilai lain dalam komponen. Saya menggunakan useRef untuk mengakses dan memanipulasi elemen input secara langsung, triggernya jika ada perubahan pada variable editing.useState
digunakan untuk membuat variabel state dalam komponen fungsional. DenganuseState
, developer dapat menyimpan dan mengubah data yang memengaruhi tampilan komponen, dan setiap perubahan akan memicu rerender komponen.
Implementasi HomeTableContainer
Sekarang saya akan membuat HomeTableContainer
, disini adalah kode logic yang akan digunakan di HomeTableView
. Saya juga memanggil function EditableContext
dan EditableCell
dari file Context.tsx
. Pada function handleValidate
saya membuat kondisi batas usia yang dapat diinput oleh user, jika usia melebih 60 thn maka akan error.
// src/components/home/table/index.tsx
import { useEffect, useState } from 'react';
import HomeTableView from './Main'
import { Form } from 'antd';
import { cloneDeep, isEmpty } from 'lodash';
import { EditableContext, EditableCell } from './Context';
const HomeTableContainer = ({
data,
isLoading,
isDisabled,
}: any) => {
const [validate, setValidate]: any = useState([]);
const [mutableData, setMutableData] = useState(data)
const [success, setSuccess] = useState<any>([]);
const handleSave = async (row: any) => {
if (isDisabled) {
return;
}
const newData = cloneDeep(data);
const index = newData.content.findIndex(
(item: any) => Number(row.id) === Number(item.id)
);
if (newData.content[index]) {
newData.content[index] = row;
setMutableData({ ...newData });
}
};
const handleValidate: any = (
index: number,
record: any,
dataIndex: string,
value: string | number
) => {
let bool = true;
if (isDisabled) {
return false;
}
const val = cloneDeep(validate);
const obj: any = {}
if ((dataIndex === 'age' &&
Number(value) > 60)) {
obj.age = {
validateStatus: 'error',
errorMsg: `Maksimal usia ${
record.name
} adalah 60th`,
};
bool = false;
}
if (!val[index]) {
val[index] = {
[dataIndex]: {}
}
}
if (obj && !isEmpty(obj) && val[index]) {
val[index] = {...obj}
} else {
val[index] = {}
}
setValidate([...val])
return bool;
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const EditableRow: any = ({ index, ...obj }: any) => {
const [form] = Form.useForm();
return (
<Form initialValues={data.content} form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...obj} />
</EditableContext.Provider>
</Form>
);
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const sharedOnCell = () => {
return {};
};
const handleSetSuccess = (val: any) => {
setSuccess(val);
};
useEffect(() => {
return () => {
setSuccess([]);
};
}, []);
return (
<HomeTableView
data={mutableData}
components={components}
isLoading={isLoading}
rowKey={'id'}
handleSave={handleSave}
success={success}
setSuccess={handleSetSuccess}
validate={validate !== undefined ? validate : []}
isDisabled={false}
handleValidate={handleValidate}
sharedOnCell={sharedOnCell}
/>
)
}
export default HomeTableContainer
Implementasi HomeTableView
Setelah membuat logic pada HomeTableContainer
, di file HomeTableView
saya mendefinisikan serta memodifikasi column dengan menempatkan function yang telah kita buat di file HomeTableContainer
sehingga, baris pada column age dapat di edit.
// src/components/home/table/Main.tsx
import { Col, Row, Table } from 'antd';
const HomeTableView = ({
data,
components,
isLoading,
onTableChange,
rowKey,
handleSave,
handleValidate,
sharedOnCell,
validate,
success,
setSuccess,
isDisabled,
}: any) => {
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '100px',
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
onCell: sharedOnCell,
editable: true,
suffix: 'th',
width: '50px',
},
{
title: 'Address',
dataIndex: 'address',
width: '100px',
key: 'address',
},
];
const mutableColumns = columns.map((col: any) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: any, index: number) => ({
record,
suffix: col.suffix,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
data,
disabled: isDisabled,
handleValidate,
validate,
success,
setSuccess,
index,
}),
};
});
return (
<>
<Row style={{ marginTop: '12px' }}>
<Col flex={'auto'}>
<Row style={{ marginBottom: '12px', marginTop: '12px' }}>
<Table
bordered
columns={mutableColumns}
components={components}
dataSource={data?.content || []}
loading={isLoading}
rowClassName={() => 'editable-row'}
onChange={onTableChange}
pagination={false}
rowKey={rowKey}
sticky
scroll={{ x: 1500 }}
style={{ width: '100%' }}
/>
</Row>
</Col>
</Row>
</>
);
};
export default HomeTableView
Output
Dibawah ini adalah hasil dari kode diatas, form input akan menampilkan error jika usia lebih dari 60thn.
Semoga sharing saya kali ini dapat bermanfaat bagi teman-teman developer untuk membuat editable cell dan table menggunakan Antd-ReactJs. Jika kalian terinspirasi dan berfikir ini dapat diimplement di projek kalian, silahkan lakukan.
Feel free to ask questions
Terima kasih