rename auto to field

This commit is contained in:
cupcakearmy 2019-10-19 08:45:14 +02:00
parent 0cf1b6e73b
commit 5d6a420283
8 changed files with 249 additions and 256 deletions

164
README.md
View File

@ -22,9 +22,9 @@ npm i formhero
### 👁 Demos ### 👁 Demos
- [__*Live Web*__](https://cupcakearmy.github.io/formhero/) - [**_Live Web_**](https://cupcakearmy.github.io/formhero/)
- [__*Live Codesandbox*__](https://codesandbox.io/embed/formhero-simple-bdcx2?expanddevtools=1&fontsize=14) - [**_Live Codesandbox_**](https://codesandbox.io/embed/formhero-simple-bdcx2?expanddevtools=1&fontsize=14)
- [__*Live React-Native*__](https://snack.expo.io/@cupcakearmy/useform) - [**_Live React-Native_**](https://snack.expo.io/@cupcakearmy/useform)
### Links ### Links
@ -35,7 +35,7 @@ npm i formhero
- [Validators](#validators) - [Validators](#validators)
- [Options](#options) - [Options](#options)
- Returns - Returns
- [auto](#auto) - [field](#field)
- [form](#form) - [form](#form)
- [errors](#errors) - [errors](#errors)
- [isValid](#isvalid) - [isValid](#isvalid)
@ -43,7 +43,7 @@ npm i formhero
## 🤔 Motivation ## 🤔 Motivation
So why write yet another form utility you might ask? First off, I don't like the Formik approach. In my humble opition formik is very verbose and requires lots of boilerplate. Also does not work with hooks. [react-hook-form](https://react-hook-form.com/) is a very cool library and it is the main inspiration for formhero. It does almost everything right... typescript, no deps, small, concise. So why write yet another form utility you might ask? First off, I don't like the Formik approach. In my humble opition formik is very verbose and requires lots of boilerplate. Also does not work with hooks. [react-hook-form](https://react-hook-form.com/) is a very cool library and it is the main inspiration for formhero. It does almost everything right... typescript, no deps, small, concise.
The problem that I found while using it was that 3rd party ui libs like [Ant Design](https://ant.design/) or [Fabric UI](https://developer.microsoft.com/en-us/fabric#/controls/web) do not always have the standart `onChange` or `value` props in their components. That is where react-hook-form starts falling apart. This is what formhero tries to address in the most minimalistic way possible, with as little code as needed. All in pure typescript and no deps. The problem that I found while using it was that 3rd party ui libs like [Ant Design](https://ant.design/) or [Fabric UI](https://developer.microsoft.com/en-us/fabric#/controls/web) do not always have the standart `onChange` or `value` props in their components. That is where react-hook-form starts falling apart. This is what formhero tries to address in the most minimalistic way possible, with as little code as needed. All in pure typescript and no deps.
@ -54,8 +54,7 @@ import ReactDOM from 'react-dom'
import { useForm } from 'formhero' import { useForm } from 'formhero'
const Form = () => { const Form = () => {
const { field, form } = useForm({
const { auto, form } = useForm({
username: '', username: '',
password: '', password: '',
}) })
@ -68,9 +67,8 @@ const Form = () => {
return ( return (
<div> <div>
<form onSubmit={_submit}> <form onSubmit={_submit}>
<input {...field('username')} />
<input {...auto('username')} /> <input {...field('password')} />
<input {...auto('password')} />
<button type="submit">Go 🚀</button> <button type="submit">Go 🚀</button>
</form> </form>
@ -85,43 +83,43 @@ const Form = () => {
```typescript ```typescript
const Form = () => { const Form = () => {
const { field, form, errors } = useForm(
const { auto, form, errors } = useForm({ {
username: '', username: '',
email: '', email: '',
password: '' password: '',
}, {
username: value => value.length > 3,
email: {
validator: /@/,
message: 'Must contain an @',
}, },
password: [ {
{ username: value => value.length > 3,
validator: /[A-Z]/, email: {
message: 'Must contain an uppercase letter' validator: /@/,
message: 'Must contain an @',
}, },
{ password: [
validator: /[\d]/, {
message: 'Must contain a digit' validator: /[A-Z]/,
}, message: 'Must contain an uppercase letter',
] },
}) {
validator: /[\d]/,
message: 'Must contain a digit',
},
],
}
)
return ( return (
<form> <form>
<h1>Errors & Validation</h1> <h1>Errors & Validation</h1>
<input {...auto('username')} placeholder="Username" /> <input {...field('username')} placeholder="Username" />
{errors.username && 'Must be longer than 3'} {errors.username && 'Must be longer than 3'}
<input {...auto('email')} placeholder="EMail" /> <input {...field('email')} placeholder="EMail" />
{errors.email} {errors.email}
<input {...auto('password')} placeholder="Password" type="password" /> <input {...field('password')} placeholder="Password" type="password" />
{errors.password} {errors.password}
</form> </form>
) )
} }
@ -133,30 +131,32 @@ Often it happens that you use a specific input or framework, so the default gett
```typescript ```typescript
const Form = () => { const Form = () => {
const { field, form, errors } = useForm({
const { auto, form, errors } = useForm({
awesome: true, awesome: true,
}) })
return ( return (
<form onSubmit={e => { <form
e.preventDefault() onSubmit={e => {
console.log(form) e.preventDefault()
}}> console.log(form)
}}
>
<h1>Custom</h1> <h1>Custom</h1>
<label> <label>
<input type="checkbox" {...auto('awesome', { <input
setter: 'checked', type="checkbox"
getter: 'onChange', {...field('awesome', {
extractor: (e) => e.target.checked setter: 'checked',
})} /> getter: 'onChange',
extractor: e => e.target.checked,
})}
/>
Is it awesome? Is it awesome?
</label> </label>
<input type="submit" /> <input type="submit" />
</form> </form>
) )
} }
@ -167,7 +167,7 @@ const Form = () => {
### `useForm` ### `useForm`
```typescript ```typescript
const {auto, errors, update, form, isValid} = useForm(initial, validators, options) const { field, errors, update, form, isValid } = useForm(initial, validators, options)
``` ```
### Initial ### Initial
@ -192,7 +192,7 @@ A validator is an object that taked in either a `RegExp` or a `Function` (can be
```javascript ```javascript
const validators = { const validators = {
// Only contains letters. // Only contains letters.
// This could also be a (also async) function that returns a boolean. // This could also be a (also async) function that returns a boolean.
username: /^[A-z]*$/, username: /^[A-z]*$/,
} }
@ -213,7 +213,7 @@ const validators = {
username: { username: {
validator: /^[A-z]*$/, validator: /^[A-z]*$/,
message: 'My custom error message', message: 'My custom error message',
} },
} }
``` ```
@ -227,12 +227,12 @@ const validators = {
message: 'My custom error message', message: 'My custom error message',
}, },
/[\d]/, /[\d]/,
async (value) => value.length > 0, async value => value.length > 0,
{ {
validator: (value) => true, validator: value => true,
message: 'Some other error', message: 'Some other error',
} },
] ],
} }
``` ```
@ -245,33 +245,30 @@ Sometimes it's practical to have some different default values when using for ex
[Check the Expo Snack for a live preview](https://snack.expo.io/@cupcakearmy/useform) [Check the Expo Snack for a live preview](https://snack.expo.io/@cupcakearmy/useform)
```javascript ```javascript
import * as React from 'react'; import * as React from 'react'
import { Text, SafeAreaView, TextInput } from 'react-native'; import { Text, SafeAreaView, TextInput } from 'react-native'
import { useForm } from 'formhero'; import { useForm } from 'formhero'
const initial = { const initial = {
username: 'i am all lowercase', username: 'i am all lowercase',
}; }
const validators = {}; const validators = {}
const options = { const options = {
setter: 'value', // This is not stricly necessarry as 'value' would already be the default. setter: 'value', // This is not stricly necessarry as 'value' would already be the default.
getter: 'onChangeText', getter: 'onChangeText',
extractor: text => text.toLowerCase(), extractor: text => text.toLowerCase(),
}; }
export default () => { export default () => {
const { form, auto } = useForm(initial, validators, options); const { form, field } = useForm(initial, validators, options)
return ( return (
<SafeAreaView> <SafeAreaView>
<TextInput <TextInput style={{ height: 40, borderColor: 'gray', borderWidth: 2 }} {...field('username')} />
style={{ height: 40, borderColor: 'gray', borderWidth: 2 }}
{...auto('username')}
/>
<Text>{form.username}</Text> <Text>{form.username}</Text>
</SafeAreaView> </SafeAreaView>
); )
}; }
``` ```
###### Example: React Native (Method 2 - Local overwrite) ###### Example: React Native (Method 2 - Local overwrite)
@ -280,15 +277,15 @@ export default () => {
// ... // ...
export default () => { export default () => {
const { form, auto } = useForm({ const { form, field } = useForm({
username: 'i am all lowercase', username: 'i am all lowercase',
}); })
return ( return (
<SafeAreaView> <SafeAreaView>
<TextInput <TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 2 }} style={{ height: 40, borderColor: 'gray', borderWidth: 2 }}
{...auto('username', { {...field('username', {
setter: 'value', // This is not stricly necessarry as 'value' would already be the default. setter: 'value', // This is not stricly necessarry as 'value' would already be the default.
getter: 'onChangeText', getter: 'onChangeText',
extractor: text => text.toLowerCase(), extractor: text => text.toLowerCase(),
@ -296,44 +293,41 @@ export default () => {
/> />
<Text>{form.username}</Text> <Text>{form.username}</Text>
</SafeAreaView> </SafeAreaView>
); )
}; }
``` ```
### Auto ### field
The `auto` object is used to bind the form state to the input.
The `field` object is used to bind the form state to the input.
###### Example: Simple ###### Example: Simple
```javascript ```javascript
const { auto } = useForm() const { field } = useForm()
<input {...auto('username')} /> <input {...field('username')} />
``` ```
###### Example: With custom options ###### Example: With custom options
All are optional. All are optional.
```javascript ```javascript
const { auto } = useForm() const { field } = useForm()
<input {...auto('username', { <input {...field('username', {
getter: 'onChage', getter: 'onChage',
setter: 'value', setter: 'value',
extractor: (e) => e.target.value extractor: (e) => e.target.value
})} /> })} />
``` ```
## Form ## Form
This is the form state that you can use when submitting the data This is the form state that you can use when submitting the data
###### Example ###### Example
```javascript ```javascript

View File

@ -4,32 +4,34 @@ import ReactDOM from 'react-dom'
import { useForm } from '../dist' import { useForm } from '../dist'
const Index: React.FC = () => { const Index: React.FC = () => {
const { field, form, errors } = useForm({
awesome: true,
})
const { auto, form, errors } = useForm({ return (
awesome: true, <form
}) onSubmit={e => {
e.preventDefault()
console.log(form)
}}
>
<h1>Custom</h1>
return ( <label>
<form onSubmit={e => { <input
e.preventDefault() type="checkbox"
console.log(form) {...field('awesome', {
}}> setter: 'checked',
getter: 'onChange',
extractor: e => e.target.checked,
})}
/>
Is it awesome?
</label>
<h1>Custom</h1> <input type="submit" />
</form>
<label> )
<input type="checkbox" {...auto('awesome', {
setter: 'checked',
getter: 'onChange',
extractor: (e) => e.target.checked
})} />
Is it awesome?
</label>
<input type="submit" />
</form>
)
} }
ReactDOM.render(<Index />, document.getElementById('custom')) ReactDOM.render(<Index />, document.getElementById('custom'))

View File

@ -4,50 +4,52 @@ import ReactDOM from 'react-dom'
import { useForm } from '../' import { useForm } from '../'
const Index: React.FC = () => { const Index: React.FC = () => {
const { field, form, errors, isValid } = useForm(
{
username: '',
email: '',
password: '',
},
{
username: value => value.length > 3,
email: {
validator: /@/,
message: 'Must contain an @',
},
password: [
{
validator: /[A-Z]/,
message: 'Must contain an uppercase letter',
},
{
validator: /[\d]/,
message: 'Must contain a digit',
},
],
}
)
const { auto, form, errors, isValid } = useForm({ return (
username: '', <form
email: '', onSubmit={e => {
password: '' e.preventDefault()
}, { if (isValid) console.log(form)
username: value => value.length > 3, }}
email: { >
validator: /@/, <h1>Errors & Validation</h1>
message: 'Must contain an @',
},
password: [
{
validator: /[A-Z]/,
message: 'Must contain an uppercase letter'
},
{
validator: /[\d]/,
message: 'Must contain a digit'
},
]
})
return ( <input {...field('username')} placeholder="Username" />
<form onSubmit={(e) => { {errors.username && 'Must be longer than 3'}
e.preventDefault()
if (isValid) console.log(form)
}}>
<h1>Errors & Validation</h1> <input {...field('email')} placeholder="EMail" />
{errors.email}
<input {...auto('username')} placeholder="Username" /> <input {...field('password')} placeholder="Password" type="password" />
{errors.username && 'Must be longer than 3'} {errors.password}
<input {...auto('email')} placeholder="EMail" /> <input type="submit" />
{errors.email} </form>
)
<input {...auto('password')} placeholder="Password" type="password" />
{errors.password}
<input type="submit" />
</form>
)
} }
ReactDOM.render(<Index />, document.getElementById('errors')) ReactDOM.render(<Index />, document.getElementById('errors'))

View File

@ -1,67 +1,64 @@
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Form Hero</title>
<style>
body {
padding: 1em;
margin: 0;
font-family: 'Courier New', Courier, monospace;
}
<head> section {
<meta charset="UTF-8"> text-align: center;
<meta name="viewport" content="width=device-width, initial-scale=1.0"> max-width: 25em;
<title>Form Hero</title> margin: 1em auto;
<style> padding: 1em;
body { background-color: #f3f3f3;
padding: 1em; border-radius: 0.5em;
margin: 0; box-shadow: 0 8px 16px -16px rgba(0, 0, 0, 0.5);
font-family: 'Courier New', Courier, monospace; }
}
section { input {
text-align: center; font-size: 1.25em;
max-width: 25em; display: block;
margin: 1em auto; padding: 0.5em 1em;
padding: 1em; margin-top: 0.5em;
background-color: #f3f3f3; border-radius: 1em;
border-radius: .5em; width: 100%;
box-shadow: 0 8px 16px -16px rgba(0, 0, 0, .5); outline: none;
} border: 0.15em solid white;
}
input { input:focus,
font-size: 1.25em; input:hover {
display: block; border-color: #31def5;
padding: .5em 1em; }
margin-top: .5em;
border-radius: 1em;
width: 100%;
outline: none;
border: .15em solid white;
}
input:focus, input[type='submit'] {
input:hover { cursor: pointer;
border-color: #31def5; }
} input[type='checkbox'] {
display: inline;
width: initial;
}
</style>
</head>
input[type="submit"] { <body>
cursor: pointer; <section>
} <h3>Open the console to see the submitted data</h3>
</section>
<section id="simple"></section>
<section id="errors"></section>
<section id="select"></section>
<section id="custom"></section>
input[type="checkbox"] { <script src="./simple.tsx"></script>
display: inline; <script src="./errorsAndValidation.tsx"></script>
width: initial; <script src="./select.tsx"></script>
} <script src="./custom.tsx"></script>
</style> </body>
</head> </html>
<body>
<section>
<h3>Open the console to see the submitted data</h3>
</section>
<section id="simple"></section>
<section id="errors"></section>
<section id="select"></section>
<section id="custom"></section>
<script src="./simple.tsx"></script>
<script src="./errorsAndValidation.tsx"></script>
<script src="./select.tsx"></script>
<script src="./custom.tsx"></script>
</body>
</html>

View File

@ -4,30 +4,29 @@ import ReactDOM from 'react-dom'
import { useForm } from '../dist' import { useForm } from '../dist'
const Index: React.FC = () => { const Index: React.FC = () => {
const { field, form, errors } = useForm({
type: 'formhero',
})
const { auto, form, errors } = useForm({ return (
type: 'formhero', <form
}) onSubmit={e => {
e.preventDefault()
console.log(form)
}}
>
<h1>Select</h1>
return ( <select {...field('type')}>
<form onSubmit={e => { <option value="redux-form">Redux-Form</option>
e.preventDefault() <option value="react-hook-forms">React-Hook-Forms</option>
console.log(form) <option value="formik">Formik</option>
}}> <option value="formhero">FormHero</option>
</select>
<h1>Select</h1> <input type="submit" />
</form>
<select {...auto('type')}> )
<option value="redux-form">Redux-Form</option>
<option value="react-hook-forms">React-Hook-Forms</option>
<option value="formik">Formik</option>
<option value="formhero">FormHero</option>
</select>
<input type="submit" />
</form>
)
} }
ReactDOM.render(<Index />, document.getElementById('select')) ReactDOM.render(<Index />, document.getElementById('select'))

View File

@ -4,27 +4,26 @@ import ReactDOM from 'react-dom'
import { useForm } from '../dist' import { useForm } from '../dist'
const Index: React.FC = () => { const Index: React.FC = () => {
const { field, form, errors } = useForm({
username: 'unicorn',
password: '',
})
const { auto, form, errors } = useForm({ return (
username: 'unicorn', <form
password: '', onSubmit={e => {
}) e.preventDefault()
console.log(form)
}}
>
<h1>Simple</h1>
return ( <input {...field('username')} placeholder="Username" />
<form onSubmit={e => { <input {...field('password')} placeholder="Password" type="password" />
e.preventDefault()
console.log(form)
}}>
<h1>Simple</h1> <input type="submit" />
</form>
<input {...auto('username')} placeholder="Username" /> )
<input {...auto('password')} placeholder="Password" type="password" />
<input type="submit" />
</form>
)
} }
ReactDOM.render(<Index />, document.getElementById('simple')) ReactDOM.render(<Index />, document.getElementById('simple'))

View File

@ -88,10 +88,10 @@ export const useForm = <T, U extends { [key in keyof T]: useFormValidatorParamet
_validate(key, extracted) _validate(key, extracted)
} }
const auto = (key: keyof T, opts: useFormOptions = {}) => ({ const field = (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, field, errors, isValid }
} }

View File

@ -6,7 +6,7 @@ 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 { field, form, errors, isValid } = useForm(
{ {
username: '', username: '',
password: '', password: '',
@ -40,20 +40,20 @@ const Index: React.FC = () => {
<div> <div>
<form onSubmit={_submit}> <form onSubmit={_submit}>
<div>Username</div> <div>Username</div>
<input className="input" {...auto('username')} /> <input className="input" {...field('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" {...field('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">
<select {...auto('type')}> <select {...field('type')}>
<option value="redux-form">Redux-Form</option> <option value="redux-form">Redux-Form</option>
<option value="react-hook-forms">React-Hook-Forms</option> <option value="react-hook-forms">React-Hook-Forms</option>
<option value="formik">Formik</option> <option value="formik">Formik</option>
@ -66,7 +66,7 @@ const Index: React.FC = () => {
<label className="checkbox"> <label className="checkbox">
<input <input
type="checkbox" type="checkbox"
{...auto('awesome', { {...field('awesome', {
setter: 'checked', setter: 'checked',
getter: 'onChange', getter: 'onChange',
extractor: e => e.target.checked, extractor: e => e.target.checked,