Skip to content

Instantly share code, notes, and snippets.

@thelamina
Created March 25, 2021 16:20
Show Gist options
  • Save thelamina/44ad7bf82731437c17d68d4771c5a65f to your computer and use it in GitHub Desktop.
Save thelamina/44ad7bf82731437c17d68d4771c5a65f to your computer and use it in GitHub Desktop.
useForm hook
import React from 'react
import { useForm } from './hooks'
export const CreateAccountForm = () => {
const { values, errors, bindField, isValid } = useForm({
initialValues: {
email: '',
phone: '',
firstName: '',
lastName: '',
password: '',
},
validations: {
firstName: {
pattern: {
value: /^\w{2,50}$/,
},
required: true,
},
lastName: {
pattern: {
value: /^\w{2,50}$/,
},
required: true,
},
email: {
required: true,
},
phone: {
pattern: {
value: /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/,
message: 'Invalid phone number',
},
required: true,
},
password: {
required: true,
pattern: {
value: /^[a-zA-Z0-9!@#$%^&*]{6,16}$/,
},
},
},
})
const handleSubmit = (e) => {
e.preventDefault()
console.log('values', values)
}
return (
<form onSubmit={handleSubmit}>
<Input
label="First Name"
error={errors.firstName}
name="firstName"
{...bindField('firstName')}
/>
<Input
label="Last Name"
name="lastName"
error={errors.lastName}
{...bindField('lastName')}
/>
<Input
label="Email"
name="email"
error={errors.email}
{...bindField('email')}
/>
<Input
label="Phone Number"
name="phone"
error={errors.phone}
{...bindField('phone')}
/>
<Input.Password
label="Password"
name="password"
error={errors.password}
{...bindField('password')}
/>
<button type="submit" disabled={!isValid()}>
Submit
</button>
</form>
)
}
import { useState } from 'react'
export const useForm = ({ validations, initialValues = {} }) => {
if (!validations) {
throw new Error('the option `validations` is required')
}
if (typeof validations !== 'object') {
throw new Error('the option `validations` should be an object')
}
if (typeof initialValues !== 'object') {
throw new Error('the option `initialValues` should be an object')
}
const [values, setValues] = useState(initialValues)
const [errors, setErrors] = useState({})
const validateField = (name, value) => {
// get the validation rules for the field
const rules = validations[name]
// check if the rules exist, since a field can not have validations
if (rules) {
// if the required rule is registered
if (rules.required) {
// now we validate the value checking if it has a value
// we are using trim, to strip whitespaces before and after the value
if (!value.trim()) {
return typeof rules.required === 'string'
? rules.required
: `${name} cannot be empty`
}
}
// if the pattern rule is registered
if (rules.pattern) {
// we execute the regex
if (!new RegExp(rules.pattern.value).exec(value)) {
// if the value does not match with the regex pattern, we try to return
// the custom message and fallback to the default message in case
return (
rules.pattern.message ||
`${value} is not a valid ${name} `
)
}
}
// if it has a validation function and its type is a function
if (rules.validate && typeof rules.validate === 'function') {
// we run the validate function with the field value
const error = rules.validate(value)
// if an error message was returned, we return it
if (error) {
return error
}
}
}
// if there are no erros, we return an empty string
return ''
}
const bindField = (name) => {
if (!name) {
throw new Error('The field name parameter is required')
}
if (name && typeof name !== 'string') {
throw new Error('The field name should be a string')
}
return {
value: values[name] || '',
onChange: (e) => {
const { value } = e.target
setValues((state) => ({
...state,
[name]: value,
}))
setErrors((state) => ({
...state,
[name]: validateField(name, value),
}))
},
}
}
const isValid = () => {
const hasErrors = Object.keys(validations).some((name) =>
Boolean(validateField(name, values[name]))
)
return !hasErrors
}
return {
values,
errors,
validateField,
bindField,
isValid,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment