Getting Started
@codeleap/form is the CodeLeap package for reusable form state, field declarations, validation, and input binding.
Use it when the app needs to standardize:
- multi-field forms declared outside component lifecycle
- standalone reusable fields
- validation attached to field definitions
- package-aware input binding through
register(...)orfield - imperative prefill, submit, reset, and first-invalid flows
- app-level field option extension through
ExtraFieldOptions
Introduction
The form system provides a structured and scalable way to manage forms using validation, default values, and field configurations.
Installing the Library
Install @codeleap/form using Bun:
bun add @codeleap/form
Initial example
Import form from the library and use it:
import { fields, form } from '@codeleap/form'
import { t } from '@lingui/core/macro'
export const example = form('example', {
input: fields.text({
placeholder: 'Placeholder',
}),
})
Choose the right form shape first
This is the main decision to make before writing code.
Use form(...) when
- the screen has multiple related inputs
- submit depends on several values together
- you need
values,isValid,validate(),firstInvalid(), orresetValues() - the UI should bind package-aware inputs through
form.register(...)
Use a standalone field when
- there is only one isolated control
- you still want validation and shared field state
- the component accepts
field={...} - you want the field definition to be reused across several places
Use plain local state when
- the input is truly local and temporary
- the package would add little value
- you do not need shared field behavior or validation helpers
register(...) vs useField(...)
These two patterns solve different problems.
- use
form.register('name')when the input component already understands CodeLeap form field props - use
field={...}for standalone reusable field flows - use
useField(...)inside a custom input component that needs to adapt afieldinto controlled UI state
Do not assume register(...) directly returns plain controlled props like value, onValueChange, error, or onBlur.
The important contract is that register(...) forwards the field prop surface expected by package-aware inputs.
Complete submit flow
This is the full pattern: declare → pre-fill on navigate → validate → submit → reset.
import { form, fields, zodValidator } from '@codeleap/form'
import { z } from 'zod'
// 1. Declare OUTSIDE any component
export const editProfileForm = form('editProfile', {
name: fields.text({
label: 'Name',
validate: zodValidator(z.string().min(2, 'Name is too short')),
}),
email: fields.text({
label: 'Email',
validate: zodValidator(z.string().email('Invalid email')),
}),
})
import { editProfileForm } from '../forms/editProfile'
// 2. Call setValues at the moment of navigation, before the screen mounts
function UserList() {
const handleEditPress = (user) => {
editProfileForm.setValues({
name: user.name,
email: user.email,
})
navigation.navigate('EditProfile', { userId: user.id })
}
// ...
}
import { editProfileForm } from '../forms/editProfile'
import { usersManager } from '../query/users'
// 3. The screen just reads and submits — no pre-fill logic here
function EditProfileScreen({ userId }) {
const updateMutation = usersManager.useUpdate()
const handleSubmit = () => {
// 4. Validate all fields
if (!editProfileForm.isValid) return
// 5. Submit with current values
updateMutation.mutate({ id: userId, ...editProfileForm.values }, {
onSuccess: () => {
// 6. Reset and go back
editProfileForm.resetValues()
navigation.goBack()
},
})
}
return (
<>
<TextInput {...editProfileForm.register('name')} />
<TextInput {...editProfileForm.register('email')} />
<Button
onPress={handleSubmit}
disabled={updateMutation.isPending}
text={updateMutation.isPending ? 'Saving...' : 'Save'}
/>
</>
)
}
Prefill rule
Prefer imperative form.setValues(...) before opening or navigating to the edit UI.
Good pattern:
- user taps Edit
- app already has the item data
- app calls
form.setValues(...) - app opens the screen or modal
Do not default to reactive prefill logic inside the form screen when the values are already known at the moment the UI is opened.
Declare the Global Type
This is used to show the additional options that can be declared per field and passed to the input.
import { TextInputProps } from 'react-native'
declare module '@codeleap/form' {
export interface ExtraFieldOptions extends TextInputFields {
placeholder?: string
label?: string
secure?: boolean
}
}
ExtraFieldOptions is a consumer-app extension point. It is how the app teaches fields about UI-specific options like labels, placeholders, descriptions, masking, disabled state, or keyboard hints.