erlangform
v0.2.1
Published
Schema-driven form builder untuk React menggunakan [TanStack Form](https://tanstack.com/form) dan [Zod](https://zod.dev/).
Readme
ErlForm
Schema-driven form builder untuk React menggunakan TanStack Form dan Zod.
ErlForm membaca struktur dari Zod schema lalu membuat field secara otomatis, sambil tetap memberikan kontrol penuh melalui fieldConfig.
Fitur
- Generate field otomatis dari Zod schema
- Integrasi dengan TanStack Form
- Konfigurasi field per nama field
- Conditional logic:
showIfdisableIfreadOnlyIfrequiredIf
clearOnHideuntuk mereset value ketika field disembunyikan- Select native dengan
options - Dynamic options berdasarkan nilai form
- Custom field component
- Section dan section configuration
- Responsive 12-column grid layout
- Array field
- Nested array
- Object field
- Label item array yang bisa statis atau dinamis berdasarkan index/item
- Add/remove label yang dapat dikustomisasi
- Nested field path yang kompatibel dengan error validation TanStack Form
- Error message dari Zod/TanStack Form ditampilkan di level field
- TypeScript support
Instalasi
npm install erlangformDependency yang dibutuhkan:
npm install @tanstack/react-form zodPenggunaan Dasar
import { useForm } from "@tanstack/react-form";
import { z } from "zod";
import { ErlForm } from "erlangform";
const schema = z.object({
username: z.string(),
email: z.string().email(),
age: z.number(),
isAdmin: z.boolean(),
});
export default function App() {
const form = useForm({
defaultValues: {
username: "",
email: "",
age: 0,
isAdmin: false,
},
validators: {
onChange: schema,
},
onSubmit: ({ value }) => {
console.log(value);
},
});
return (
<>
<ErlForm form={form} schema={schema} />
<button type="button" onClick={() => form.handleSubmit()}>
Submit
</button>
</>
);
}ErlForm menggunakan Zod schema sebagai sumber struktur form. Validasi tetap dapat dijalankan oleh TanStack Form menggunakan schema yang sama.
Konfigurasi Field
Gunakan fieldConfig untuk mengubah tampilan atau perilaku field.
<ErlForm
form={form}
schema={schema}
fieldConfig={{
username: {
label: "Username",
placeholder: "Masukkan username",
},
email: {
label: "Email",
placeholder: "Masukkan email",
},
age: {
label: "Umur",
},
}}
/>Properti FieldOptions
| Properti | Keterangan |
| ---------------------- | --------------------------------------------------------- |
| label | Label field, bisa string atau resolver berdasarkan values |
| type | text, password, textarea, number, atau checkbox |
| placeholder | Placeholder field |
| description | Deskripsi field |
| options | Option select, bisa statis atau resolver |
| component | Custom component |
| showIf | Menentukan apakah field ditampilkan |
| disableIf | Menentukan apakah field disabled |
| readOnlyIf | Menentukan apakah field readonly |
| requiredIf | Menentukan apakah field required |
| clearOnHide | Reset value ketika field menjadi hidden |
| section | Nama section |
| layout | Konfigurasi grid |
| className | Class untuk control |
| wrapperClassName | Class wrapper field |
| labelClassName | Class label |
| descriptionClassName | Class description |
| errorClassName | Class error |
| array | Konfigurasi array field |
Conditional Logic
Conditional logic menggunakan nilai form saat ini.
showIf
Menampilkan field hanya ketika kondisi terpenuhi.
fieldConfig={{
companyName: {
label: "Nama Perusahaan",
showIf: (values) => values.isCompany,
},
}}disableIf
fieldConfig={{
username: {
label: "Username",
disableIf: (values) => values.locked,
},
}}readOnlyIf
fieldConfig={{
username: {
label: "Username",
readOnlyIf: (values) => values.locked,
},
}}requiredIf
fieldConfig={{
companyName: {
label: "Nama Perusahaan",
requiredIf: (values) => values.isCompany,
},
}}clearOnHide
Jika field disembunyikan dan clearOnHide bernilai true, value field akan direset.
fieldConfig={{
companyName: {
label: "Nama Perusahaan",
showIf: (values) => values.isCompany,
clearOnHide: true,
},
}}Dengan demikian value lama tidak tetap tersimpan ketika field sudah tidak relevan.
Resolver untuk Label, Placeholder, Description, dan Options
Beberapa konfigurasi dapat berupa nilai langsung atau function yang menerima values.
fieldConfig={{
label: (values) =>
values.isCompany ? "Nama Perusahaan" : "Nama Lengkap",
placeholder: (values) =>
values.isCompany
? "Masukkan nama perusahaan"
: "Masukkan nama lengkap",
}}Dynamic options:
fieldConfig={{
city: {
label: "Kota",
options: (values) =>
values.country === "id"
? [
{ label: "Jakarta", value: "jakarta" },
{ label: "Bandung", value: "bandung" },
]
: [
{ label: "Tokyo", value: "tokyo" },
{ label: "Osaka", value: "osaka" },
],
},
}}Select
String field dapat menjadi native <select> dengan memberikan options.
const schema = z.object({
country: z.string(),
});
<ErlForm
form={form}
schema={schema}
fieldConfig={{
country: {
label: "Negara",
options: [
{ label: "Indonesia", value: "id" },
{ label: "Jepang", value: "jp" },
],
},
}}
/>;Jika options tidak diberikan, string field tetap menjadi input text biasa.
Custom Component
Field tertentu dapat diganti dengan component sendiri.
function MySelect({ field }: any) {
return (
<select
value={field.state.value ?? ""}
onChange={(event) => field.handleChange(event.target.value)}
>
<option value="id">Indonesia</option>
<option value="jp">Jepang</option>
</select>
);
}Kemudian:
<ErlForm
form={form}
schema={schema}
fieldConfig={{
country: {
label: "Negara",
component: MySelect,
},
}}
/>Custom component menerima field dan config.
Section
Field dapat dikelompokkan berdasarkan nama section.
fieldConfig={{
username: {
label: "Username",
section: "Account",
},
password: {
label: "Password",
section: "Account",
type: "password",
},
email: {
label: "Email",
section: "Profile",
},
}}Field dengan nama section yang sama akan dirender dalam section yang sama.
Section juga dapat dikonfigurasi:
<ErlForm
form={form}
schema={schema}
sectionConfig={{
Account: {
title: "Informasi Akun",
description: "Kelola pengaturan akun.",
collapsible: true,
defaultOpen: true,
},
}}
/>Layout
ErlForm menggunakan grid 12 kolom.
fieldConfig={{
username: {
layout: {
colSpan: 6,
},
},
password: {
layout: {
colSpan: 6,
},
},
bio: {
layout: {
colSpan: 12,
},
},
}}Responsive breakpoint juga dapat digunakan:
layout: {
colSpan: 12,
sm: 12,
md: 6,
lg: 4,
xl: 3,
}Contoh:
┌──────────────────┬──────────────────┐
│ Username │ Password │
│ 6 cols │ 6 cols │
├─────────────────────────────────────┤
│ Bio │
│ 12 cols │
└─────────────────────────────────────┘Array
ErlForm mendukung array dari Zod.
const schema = z.object({
contacts: z.array(
z.object({
type: z.string(),
phone: z.string(),
active: z.boolean(),
}),
),
});Default value:
const form = useForm({
defaultValues: {
contacts: [
{
type: "personal",
phone: "",
active: true,
},
],
},
validators: {
onChange: schema,
},
onSubmit: ({ value }) => {
console.log(value);
},
});Konfigurasi array:
fieldConfig={{
contacts: {
label: "Contacts",
array: {
itemLabel: "Contact",
addLabel: "Tambah Contact",
removeLabel: "Hapus Contact",
},
},
}}Hasilnya setiap item memiliki header dan tombol remove, serta tombol add di bagian bawah.
Label Item Array Dinamis
itemLabel dapat berupa function:
fieldConfig={{
companies: {
label: "Companies",
array: {
itemLabel: (item, index) =>
item.name
? `Company: ${item.name}`
: `Company ${index + 1}`,
addLabel: "Tambah Company",
removeLabel: "Hapus Company",
},
},
}}Function menerima:
(item, index);sehingga label dapat mengikuti value item saat ini.
Contoh:
Company: Erlangga
Company: Acme
Company 3Jika itemLabel hanya string:
array: {
itemLabel: "Contact",
}maka hasilnya:
Contact 1
Contact 2
Contact 3Nested Array
Array dapat berisi object yang memiliki array lain.
const schema = z.object({
companies: z.array(
z.object({
name: z.string(),
contacts: z.array(
z.object({
type: z.string(),
phone: z.string(),
active: z.boolean(),
}),
),
}),
),
});Contoh konfigurasi:
fieldConfig={{
companies: {
label: "Companies",
array: {
itemLabel: (item, index) =>
item.name
? `Company: ${item.name}`
: `Company ${index + 1}`,
addLabel: "Tambah Company",
removeLabel: "Hapus Company",
},
},
contacts: {
label: "Contacts",
array: {
itemLabel: "Contact",
addLabel: "Tambah Contact",
removeLabel: "Hapus Contact",
},
},
}}ErlForm akan membentuk nested path seperti:
companies[0].name
companies[0].contacts[0].type
companies[0].contacts[0].phone
companies[0].contacts[1].typeFormat ini juga digunakan agar error validation nested dari TanStack Form dapat terhubung kembali ke field yang sesuai.
Object Field
Object di dalam Zod schema juga didukung.
const schema = z.object({
profile: z.object({
firstName: z.string(),
lastName: z.string(),
}),
});Field di dalam object akan dirender berdasarkan struktur object tersebut.
Validation dan Error
ErlForm menggunakan TanStack Form untuk state dan validation, sementara Zod dapat digunakan sebagai validator.
const schema = z.object({
email: z.string().email("Format email tidak valid"),
contacts: z.array(
z.object({
type: z.string().min(1, "Type wajib dipilih"),
phone: z.string().min(10, "Phone minimal 10 karakter"),
}),
),
});
const form = useForm({
defaultValues: {
email: "",
contacts: [
{
type: "",
phone: "",
},
],
},
validators: {
onChange: schema,
},
onSubmit: ({ value }) => {
console.log(value);
},
});Error akan ditampilkan pada field yang sesuai.
Untuk nested array, misalnya:
companies[0].contacts[0].typeerror dari Zod juga akan dipetakan ke field tersebut.
Contoh error:
Type wajib dipilihSubmit Invalid
Jika ingin melihat error ketika submit gagal:
const form = useForm({
// ...
validators: {
onChange: schema,
},
onSubmitInvalid: (props) => {
console.log(props.formApi.getAllErrors());
},
onSubmit: ({ value }) => {
console.log(value);
},
});getAllErrors() dapat menghasilkan error dengan path seperti:
email
contacts[0].type
companies[0].contacts[0].type
companies[0].contacts[0].phoneField Error Styling
Error dapat diberi class sendiri:
fieldConfig={{
email: {
label: "Email",
errorClassName: "text-red-500 text-sm",
},
}}Class lain yang tersedia:
className
wrapperClassName
labelClassName
descriptionClassName
errorClassNameTypeScript
ErlForm dibuat dengan TypeScript dan menggunakan Zod sebagai sumber struktur form.
const schema = z.object({
username: z.string(),
email: z.string().email(),
});Schema tetap menjadi sumber utama untuk:
- Struktur field.
- Tipe field dasar.
- Validasi.
Sedangkan fieldConfig digunakan untuk menambahkan konfigurasi UI dan behavior.
Struktur Konsep
Secara umum alur ErlForm:
Zod Schema
↓
infer()
↓
Field Metadata
↓
FieldRenderer
├── Field
├── ObjectField
└── FieldArray
↓
TanStack Form
↓
Field State + ValidationConditional state:
form values
↓
resolveFieldState()
↓
visible / disabled / readOnly / requiredResolver configuration:
fieldConfig
↓
resolveFieldConfig()
↓
label / placeholder / description / optionsRequirements
- React 18 atau React 19
- TanStack Form
- Zod
Versi
Versi saat ini:
0.2.0ErlForm masih dalam tahap pengembangan aktif.
Lisensi
MIT
