Created
March 25, 2021 16:20
-
-
Save thelamina/44ad7bf82731437c17d68d4771c5a65f to your computer and use it in GitHub Desktop.
useForm hook
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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