Berdamai Dengan Multiple Props Pada Reusable Component
Zakiego
@zakiego
Latar Belakang
Masalah ini bermula karena saya sangat banyak mengerjakan project dengan banyak (sekali) input. Sudah tidak lagi bisa dibilang, dari input yang sederhana sampai bertingkat tujuh! Kompleks, satu input terkait dengan input yang lain.
Namun, input-input tersebut memiliki satu pola yang sama, yaitu pasti memiliki element <label>
dan <input>
, akhirnya agar lebih rapi dan bisa digunakan berulang tanpa harus menulis dari awal, maka dibuatlah satu component yang reusable.
Biasanya component ini diletakkan di
/components/UI/Form/Input.tsx
Komponen yang sifatnya agnostik, dalam artian tidak terikat dengan hal apa pun, bisa digunakan di mana saja, diletakkan di
UI
.
Sebut saja nama component tersebut adalah <FormInput/>
. Kita kesampingkan dulu masalah styling, maka kodenya akan seperti ini:
interface FormInputProps {
label: string
name: string
}
const FormInput: React.FC<FormInputProps> = (props) => {
return (
<div>
<label htmlFor={props.name}>{props.label}</label>
<div>
<input id={props.name} name={props.name} />
</div>
</div>
)
}
const Page = () => {
return (
<div>
<FormInput label="Name" name="name" />
</div>
)
}
<FormInput/>
adalah sebuah komponen yang akan menerima dan label dan name untuk sebagai props.
Satu masalah terselesaikan. Kita bisa menggunakan <FormInput/>
di mana saja, tanpa harus menulis <label> dan <input> berulang-ulang.
Masalah Baru
Tapi kemudian muncul masalah baru, bagaimana jika ingin meng-customize bagian <label>
dan input <input>
?
Misalnya kita ingin labelnya berwarna biru, dan inputnya berwarna hijau?
Mari kita coba menambahkan className sebagai props:
interface FormInputProps {
label: string
name: string
className?: string
}
const FormInput: React.FC<FormInputProps> = (props) => {
return (
<div>
<label htmlFor={props.name} className={props.className}>
{props.label}
</label>
<div>
<input id={props.name} name={props.name} className={props.className} />
</div>
</div>
)
}
const Page = () => {
return (
<div>
<FormInput
label="Name"
name="name"
className="text-blue-500 text-green-500" // PERHATIKAN INI
/>
</div>
)
}
Lihat kode di atas pada bagian className miliki komponen FormInput, bagaimana jadinya satu className yaitu text-blue-500 text-green-500
diletakkan pada className yang sama, kemudian digunakan untuk <label>
dan <input>
?
Yang terjadi justru adalah text-green-500
yang akan diambil, kemudian mengubah warna label dan input menjadi sama-sama hijau.
Bagaimana jika pisahkan, antara className label dan input? Sehingga propsnya akan terpisah.
interface FormInputProps {
label: string
name: string
classNameLabel?: string
classNameInput?: string
}
const FormInput: React.FC<FormInputProps> = (props) => {
return (
<div>
<label htmlFor={props.name} className={props.classNameLabel}>
{props.label}
</label>
<div>
<input
id={props.name}
name={props.name}
className={props.classNameInput}
/>
</div>
</div>
)
}
const Page = () => {
return (
<div>
<FormInput
label="Name"
name="name"
classNameLabel="text-blue-500"
classNameInput="text-green-500"
/>
</div>
)
}
Bagus!
Sekarang label akan menjadi biru, dan input menjadi hijau. Component sudah menjadi lebih customable.
Masalah Lain
Satu element <FormInput>
sudah beres. Tapi kemudian, saya ingin kembali membuat <FormInput>
dengan type password?
Rasa-rasanya akan menjadi kian kompleks, saya perlu menambahkan satu-persatu props yang diperlukan. Akhirnya component tidak se-elastis yang kita inginkan.
interface FormInputProps {
label: string
name: string
classNameLabel?: string
classNameInput?: string
type?: string
}
const FormInput: React.FC<FormInputProps> = (props) => {
return (
<div>
<label htmlFor={props.name} className={props.classNameLabel}>
{props.label}
</label>
<div>
<input
id={props.name}
name={props.name}
className={props.classNameInput}
type={props.type}
/>
</div>
</div>
)
}
const Page = () => {
return (
<div>
<FormInput
label="Name"
name="name"
classNameInput="text-blue-500"
classNameLabel="text-green-500"
/>
<FormInput
label="Password"
name="password"
classNameInput="text-blue-500"
classNameLabel="text-green-500"
type="password"
/>
</div>
)
}
Kode akan menjadi seperti di atas, props terlihat tidak natural dan kurang enak dibaca.
Jalan Keluar
Kita bisa membuat props lebih natural dan fleksibel, tanpa harus menambahkannya satu-persatu. Bagaimana caranya?
Metode ini saya temukan ketika sedang melakukan riset untuk bermigrasi dari formik ke react-hook-form. Saat melakukan riset mengenai cara membuat reusable component di react-hook-form, saya menemukan kode dari repository ini input-control/index.tsx.
Kita cukup membuat props berupa labelProps
dan inputProps
yang nantinya akan diisi dengan object kemudian dimasukkan dengan spread syntax (...).
interface FormInputProps {
label: string
name: string
labelProps?: React.DetailedHTMLProps<
React.LabelHTMLAttributes<HTMLLabelElement>,
HTMLLabelElement
>
inputProps?: React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>
}
const FormInput: React.FC<FormInputProps> = (props) => {
return (
<div>
<label htmlFor={props.name} {...props.labelProps}>
{props.label}
</label>
<div>
<input id={props.name} name={props.name} {...props.inputProps} />
</div>
</div>
)
}
const Page = () => {
return (
<div>
<FormInput
label="Name"
name="name"
inputProps={{
className: 'text-blue-500',
}}
labelProps={{
className: 'text-green-500',
}}
/>
<FormInput
label="Password"
name="password"
inputProps={{
className: 'text-blue-500',
type: 'password',
onChange: (e) => {
console.log('Password changed:', e.target.value)
},
autoComplete: 'password',
}}
labelProps={{
className: 'text-green-500',
}}
/>
</div>
)
}
Selesai!
Sekarang hanya ada dua props yang perlu kita isi dan bisa dicustom sekehandak hati. Kita bisa memasukkan props yang biasa digunakan di label dan input tanpa takut overlap satu sama lain. Bahkan di contoh di atas, kita bisa memasukkan props onChange
dan type
.
Penutup
Mengenai type pada interface, bisa kita bicarakan di lain waktu. Akhir kata, ketika membuat reusable component, kita perlu membuatnya dengan ketat namun tetap fleksibel dan terhindar dari error. Agar kita tidak menulis kode yang sama berulang, kode mudah dibaca, namun sekaligus tidak ada error yang mengganggu tidur.