This commit is contained in:
cupcakearmy 2019-10-19 08:36:50 +02:00
parent d7a3d73512
commit 544dada3c4
4 changed files with 129 additions and 128 deletions

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
semi: false
singleQuote: true
trailingComma: es5
tabWidth: 2
printWidth: 200

View File

@ -1,21 +1,19 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
export type useFormExtractor = (from: any) => any export type useFormExtractor = (from: any) => any
export type useFormOptions = { export type useFormOptions = {
extractor?: useFormExtractor, extractor?: useFormExtractor
getter?: string, getter?: string
setter?: string, setter?: string
} }
export type useFormValidatorFunction = ((s: any) => boolean | Promise<boolean>) export type useFormValidatorFunction = (s: any) => boolean | Promise<boolean>
export type useFormValidatorMethod = useFormValidatorFunction | RegExp export type useFormValidatorMethod = useFormValidatorFunction | RegExp
export type useFormValidatorObject = { export type useFormValidatorObject = {
validator: useFormValidatorMethod, validator: useFormValidatorMethod
message?: string, message?: string
} }
export type useFormValidator = useFormValidatorMethod | useFormValidatorObject export type useFormValidator = useFormValidatorMethod | useFormValidatorObject
@ -26,82 +24,74 @@ export const HTMLInputExtractor: useFormExtractor = (e: React.FormEvent<HTMLInpu
export const HTMLCheckboxExtractor: useFormExtractor = (e: React.FormEvent<HTMLInputElement>) => e.currentTarget.checked export const HTMLCheckboxExtractor: useFormExtractor = (e: React.FormEvent<HTMLInputElement>) => e.currentTarget.checked
function isFormValidatorObject(validator: useFormValidatorMethod | useFormValidatorObject): validator is useFormValidatorObject { function isFormValidatorObject(validator: useFormValidatorMethod | useFormValidatorObject): validator is useFormValidatorObject {
return validator.constructor.name === 'Object' return validator.constructor.name === 'Object'
} }
const defaultErrorMessage = (key: any) => `Error in ${key}` const defaultErrorMessage = (key: any) => `Error in ${key}`
export const useForm = <T, U extends { [key in keyof T]: useFormValidatorParameter }, E extends { [key in keyof U]?: string }>(init: T, validators: Partial<U> = {}, options: useFormOptions = {}) => { export const useForm = <T, U extends { [key in keyof T]: useFormValidatorParameter }, E extends { [key in keyof U]?: string }>(init: T, validators: Partial<U> = {}, options: useFormOptions = {}) => {
const [form, setForm] = useState<T>(init) const [form, setForm] = useState<T>(init)
const [errors, setErrors] = useState<Partial<E>>({}) const [errors, setErrors] = useState<Partial<E>>({})
const [isValid, setIsValid] = useState(true); const [isValid, setIsValid] = useState(true)
useEffect(() => { useEffect(() => {
setIsValid(!Object.values(errors).reduce((acc, cur) => acc || cur !== undefined, false)) setIsValid(!Object.values(errors).reduce((acc, cur) => acc || cur !== undefined, false))
}, [errors]) }, [errors])
const _set = (key: keyof T, value: any) => { const _set = (key: keyof T, value: any) => {
setForm({ setForm({
...form, ...form,
[key]: value, [key]: value,
}) })
} }
const _validateAll = async (value: any, object: useFormValidator): Promise<boolean> => { const _validateAll = async (value: any, object: useFormValidator): Promise<boolean> => {
const validator = isFormValidatorObject(object) ? object.validator : object const validator = isFormValidatorObject(object) ? object.validator : object
if (validator.constructor.name === 'Function') if (validator.constructor.name === 'Function') return (validator as useFormValidatorFunction)(value)
return (validator as useFormValidatorFunction)(value) else if (validator.constructor.name === 'AsyncFunction') return await (validator as useFormValidatorFunction)(value)
else if (validator.constructor.name === 'AsyncFunction') else if (validator.constructor.name === 'RegExp') return (validator as RegExp).test(value)
return await (validator as useFormValidatorFunction)(value) else return false
else if (validator.constructor.name === 'RegExp') }
return (validator as RegExp).test(value)
else return false
}
const _validate = (key: keyof T, value: any) => { const _validate = (key: keyof T, value: any) => {
const validator: useFormValidatorParameter | undefined = validators[key] const validator: useFormValidatorParameter | undefined = validators[key]
if (!validator) return if (!validator) return
if (Array.isArray(validator)) { if (Array.isArray(validator)) {
Promise.all(validator.map(v => _validateAll(value, v))) Promise.all(validator.map(v => _validateAll(value, v))).then(result => {
.then(result => { const index = result.indexOf(false)
const index = result.indexOf(false) setErrors({
setErrors({ ...errors,
...errors, [key]:
[key]: index === -1 index === -1
? undefined ? undefined
: isFormValidatorObject(validator[index]) && (validator[index] as useFormValidatorObject).message : isFormValidatorObject(validator[index]) && (validator[index] as useFormValidatorObject).message
? (validator[index] as useFormValidatorObject).message ? (validator[index] as useFormValidatorObject).message
: defaultErrorMessage(key) : defaultErrorMessage(key),
}) })
}) })
} else { } else {
_validateAll(value, validator) _validateAll(value, validator).then(valid => {
.then(valid => { setErrors({
setErrors({ ...errors,
...errors, [key]: valid ? undefined : isFormValidatorObject(validator) && validator.message ? validator.message : defaultErrorMessage(key),
[key]: valid })
? undefined })
: isFormValidatorObject(validator) && validator.message }
? validator.message }
: defaultErrorMessage(key)
})
})
}
}
const update = (key: keyof T, extractor = options.extractor) => (value: any) => { const update = (key: keyof T, extractor = options.extractor) => (value: any) => {
const extracted = extractor ? extractor(value) : HTMLInputExtractor(value) const extracted = extractor ? extractor(value) : HTMLInputExtractor(value)
_set(key, extracted) _set(key, extracted)
_validate(key, extracted) _validate(key, extracted)
} }
const auto = (key: keyof T, opts: useFormOptions = {}) => ({ const auto = (key: keyof T, opts: useFormOptions = {}) => ({
[opts.getter || options.getter || 'onChange']: update(key, opts.extractor), [opts.getter || options.getter || 'onChange']: update(key, opts.extractor),
[opts.setter || options.setter || 'value']: form[key] as any, [opts.setter || options.setter || 'value']: form[key] as any,
}) })
return { form, update, auto, errors, isValid } return { form, update, auto, errors, isValid }
} }

View File

@ -1,22 +1,19 @@
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Form Hero</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css" />
<style>
body {
padding: 1em;
}
</style>
</head>
<head> <body>
<meta charset="UTF-8"> <div id="root"></div>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./test.tsx"></script>
<meta http-equiv="X-UA-Compatible" content="ie=edge"> </body>
<title>Form Hero</title> </html>
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css" />
<style>
body {
padding: 1em
}
</style>
</head>
<body>
<div id="root"></div>
<script src="./test.tsx"></script>
</body>
</html>

View File

@ -3,32 +3,33 @@ import ReactDOM from 'react-dom'
import { useForm } from '../' import { useForm } from '../'
const TextError: React.FC<{ error?: string }> = ({ error }) => (!error ? null : <div className="has-text-danger">{error}</div>)
const TextError: React.FC<{ error?: string }> = ({ error }) => !error
? null
: <div className="has-text-danger">{error}</div>
const Index: React.FC = () => { const Index: React.FC = () => {
const { auto, form, errors, isValid } = useForm(
const { auto, form, errors, isValid } = useForm({ {
username: '', username: '',
password: '', password: '',
type: 'formhero', type: 'formhero',
awesome: true, awesome: true,
}, {
username: [
/^test/,
{
validator: async () => { return true },
message: 'Digits please',
}
],
password: {
validator: /^.{3,}$/,
message: 'To short',
}, },
awesome: (value) => !!value {
}) username: [
/^test/,
{
validator: async () => {
return true
},
message: 'Digits please',
},
],
password: {
validator: /^.{3,}$/,
message: 'To short',
},
awesome: value => !!value,
}
)
const _submit = (e: React.FormEvent) => { const _submit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
@ -41,12 +42,14 @@ const Index: React.FC = () => {
<div>Username</div> <div>Username</div>
<input className="input" {...auto('username')} /> <input className="input" {...auto('username')} />
<TextError error={errors.username} /> <TextError error={errors.username} />
<br /><br /> <br />
<br />
<div>Password</div> <div>Password</div>
<input className="input" {...auto('password')} /> <input className="input" {...auto('password')} />
<TextError error={errors.password} /> <TextError error={errors.password} />
<br /><br /> <br />
<br />
<div>Which one to choose?</div> <div>Which one to choose?</div>
<div className="select"> <div className="select">
@ -57,21 +60,27 @@ const Index: React.FC = () => {
<option value="formhero">FormHero</option> <option value="formhero">FormHero</option>
</select> </select>
</div> </div>
<br /><br /> <br />
<br />
<label className="checkbox"> <label className="checkbox">
<input type="checkbox" {...auto('awesome', { <input
setter: 'checked', type="checkbox"
getter: 'onChange', {...auto('awesome', {
extractor: (e) => e.target.checked setter: 'checked',
})} /> getter: 'onChange',
extractor: e => e.target.checked,
})}
/>
Is it awesome? Is it awesome?
</label> </label>
<TextError error={errors.awesome} /> <TextError error={errors.awesome} />
<br /><br /> <br />
<br />
<button className="button" type="submit">Go 🚀</button> <button className="button" type="submit">
Go 🚀
</button>
</form> </form>
</div> </div>
) )
@ -79,5 +88,5 @@ const Index: React.FC = () => {
ReactDOM.render(<Index />, document.getElementById('root')) ReactDOM.render(<Index />, document.getElementById('root'))
// @ts-ignore // @ts-ignore
// if (module.hot) module.hot.accept() // if (module.hot) module.hot.accept()