formular
v3.1.6
Published
Build forms in React. Easy-Peasy!
Downloads
602
Readme
Formular
Easy way to work with forms in React. Using React Hooks 😏
Installation
npm install formular
Philosophy
There are many form libraries that works out of the box - "import Form, Field and that's it". But in most projects common fields are customized by design and usage of Form, Field components become impossible. Formular doesn't provide inboxing components for fast start, but it provides easy way to attach form functionality to custimized fields!
For example you have your own styled Input
component with specific logic inside
import { useField, useFieldState } from 'formular'
const FormularInput = () => {
const field = useField()
const state = useFieldState(field)
return <CustomInput value={state.value} onChange={field.set} />
}
useField
is a wrapper for new Field
:
const useField = (opts, deps) => useMemo(() => new Field(opts), deps || [])
So when field's state updates FormularSelect doesn't render. Here useFieldState
comes, it triggers react state update which call component's render.
This is Field, and whats about Form?
Lets update the code above to reuse Select component
import { useField, useFieldState } from 'formular'
const FormularInput = ({ field }) => {
const state = useFieldState(field)
return <CustomInput value={state.value} onChange={field.set} />
}
const Auth = () => {
const emailField = useField()
const passwordField = useField()
return (
<>
<FormularInput field={emailField} />
<FormularInput field={passwordField} />
</>
)
}
Let's add validators and submit logic
const required = (value) => {
if (!value) {
return 'Required'
}
}
const Auth = () => {
const emailField = useField({
validate: [ required ],
})
const passwordField = useField({
validate: [ required ],
})
const handleSubmit = useCallbac(async () => {
const isEmailValid = await emailField.validate()
const isPasswordValid = await passwordField.validate()
const emailValue = emailField.state.value
const passwordValue = passwordField.state.value
const emailError = emailField.state.error
const passwordError = passwordField.state.error
}, [])
return (
<>
<FormularInput field={emailField} />
<FormularInput field={passwordField} />
<button onClick={handleSubmit}>Login</button>
</>
)
}
Same could be written using useForm
const Auth = () => {
const form = useForm({
email: [ required ],
password: [ required ],
})
const handleSubmit = useCallbac(async () => {
const isValid = await form.validate()
const values = form.getValues() // { email: '', password: '' }
const errors = form.getErrors() // { email: 'Required', password: 'Required' }
}, [])
return (
<>
<FormularInput field={emailField} />
<FormularInput field={passwordField} />
<button onClick={handleSubmit}>Login</button>
</>
)
}
in most cases you need submit
const handleSubmit = useCallbac(async () => {
try {
const values = await form.submit() // { email: '', password: '' }
}
catch (errors) {} // { email: 'Required', password: 'Required' }
}, [])
Validation
const required = (value) => {
if (!value) {
return 'Required'
}
}
const uniqueEmail = async (value) => {
const isExist = await fetch(`check-email-exist?email={value}`)
if (isExists) {
return 'Account with such email already exists'
}
}
const field = useField({
validate: [ required, uniqueEmail ]
})
const isValid = await field.validate()
☝️ field.validate()
always async!
☝️ a validator function should return undefined
if value is valid
Examples
Options
Form
type FormOpts = {
name?: string
fields: {
[key: string]: FieldOpts
}
initialValues?: object
}
Field
type FieldOpts = {
name?: string
value?: string // initial value
validate?: Array<Function> // list of validators
readOnly?: boolean
validationDelay?: number // adds debounce to validation
}
Interfaces
Form
type FormEntity = {
name?: string
opts: FormOpts
fields: {
[key: string]: Field
}
state: {
isValid: boolean
isChanged: boolean
isValidating: boolean
isSubmitting: boolean
isSubmitted: boolean
}
setState(values: Partial<State>): void
setValues(values: object): void
getValues(): object
unsetValues(): void
getErrors(): object
async validate(): Promise<boolean>
async submit(): Promise<object>
on(eventName: string, handler: Function): void
off(eventName: string, handler: Function): void
}
const form: FormEntity = useForm(opts)
Field
type FieldEntity = {
form?: Form
name?: string
opts: FieldOpts
node?: HTMLElement
validators: Array<Function>
readOnly: boolean
debounceValidate: Function // method to call validation with debounce
state: {
value: any
error: any
isChanged: boolean
isValidating: boolean
isValidated: boolean
isValid: boolean
}
setState(values: Partial<State>): void
setRef(node: HTMLElement): void
unsetRef(): void
set(value: any, opts?: { silent: boolean }): void
unset(): void
validate = (): CancelablePromise
on(eventName: string, handler: Function): void
off(eventName: string, handler: Function): void
}
const field: FieldEntity = useField(opts)
Note: { silent: true }
in field.set doesn't trigger events.
Events
Form
form.on('state change', (state) => {
// triggers on a form's state change
})
form.on('change', (field) => {
// triggers on a field change
})
form.on('blur', (field) => {
// triggers on a field blurring
})
Field
form.on('state change', (state) => {
// triggers on a field's state change
})
field.on('set', (value) => {
// triggers on a value set
})
field.on('change', (value) => {
// simlink to "set"
})
field.on('unset', () => {
// triggers on a value unset
})
field.on('start validate', () => {
// triggers on a validation start
})
field.on('validate', (error) => {
// triggers on a validation finish
})
field.on('blur', () => {
// triggers on a field blurring
})