- Notifications
You must be signed in to change notification settings - Fork0
A React hook 🎣 for easy form handling
License
balazsorban44/use-form
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This should be installed as one of your projectdependencies:
yarn add another-use-form-hookor
npm install --save another-use-form-hookNOTE:
another-use-form-hookonly works withreact >=16.8, since it is a hook.
This hook is intended to give a full solution for handling forms. From interdependent field value validations (meaning if a field value is dependent on other field value), to submitting the form, and providing information about when the UI should be unresponsive (loading of some kind of async-like operation), in addition to notification "hooks" to be able to inform the users the most efficient way.
To retrieve props for an input field, you have the following options:
- Using the
form.inputs.{inputType}('name')input prop generator function (inputTypesis one ofthese) - Using
form.fields.{name}.{value|error}andform.handleChangefunctions
NOTE: The example below is available live atCodeSandbox
Let's see a complex example to understand how it works:
importReactfrom"react";importReactDOMfrom"react-dom";importuseFormfrom"another-use-form-hook";import"./styles.css";/** * NOTE: We are using date-fns for this example, * but it is absolutly not a requirement. */import{addDays,isAfter,differenceInDays,parseISO}from"date-fns";constisValidEmail=email=>/\w*@\w*\.\w*/.test(email);// Do better 💩constTODAY=newDate();constApp=()=>{constform=useForm({initialState:{email:"",arrival:TODAY,departure:TODAY},validators:(fields,isSubmitting)=>({// if not submitting, don't throw errors for invalid e-mail, like an empty fieldemail:isSubmitting ?isValidEmail(fields.email) :typeoffields.email==="string",// earliest arrival must be tomorrowarrival:isAfter(parseISO(fields.arrival),TODAY),// earliest departure must be after tomorrowdeparture:isAfter(addDays(parseISO(fields.departure),1),TODAY),// departure must be at least a day after arrivalminOneNight:isSubmitting ?differenceInDays(parseISO(fields.departure),parseISO(fields.arrival))>=1 :true}),onNotify:(type,reason)=>{// you can use type and reason to send specific notifications to the userswitch(type){case"submitError":console.error("Form could not be submitted: ",reason);break;case"submitSuccess":console.info("Form has been submitted.",reason);break;case"validationErrors":console.warn("The following problems occurred while validating: ",reason);break;default:break;}},onSubmit:async({ fields, setLoading, notify})=>{try{setLoading(true);// submitting the form, eg.: fetch("path/to/my/submit")constresponse=awaitnewPromise(resolve=>{setTimeout(()=>{console.log("Submitting: ",fields);resolve(fields);},1000);});notify("submitSuccess",response);}catch(error){notify("submitError",error.message);}finally{setLoading(false);}}});return(<formonSubmit={form.handleSubmit}>{/* option 1 (control all the props with a one-liner)*/}<fieldset><legend>Option 1</legend><labelhtmlFor="email">{form.fields.email.error ?"Invalid" :""} email</label><input{...form.inputs.email("email")}/><labelhtmlFor="departure">{form.fields.arrival.error ?"Invalid" :""} arrival</label><input{...form.inputs.date("arrival")}// You can override props by simply defining them lastonChange={e=>form.handleChange(e,["minOneNight"])}/></fieldset>{/* option 2 specify id, type, name, value props manually*/}<fieldset><legend>Option 2</legend><labelhtmlFor="arrival">{form.fields.arrival.error ?"Invalid" :""} arrival</label><inputtype="date"id="arrival"name="arrival"value={form.fields.arrival.value}onChange={e=>form.handleChange(e,["minOneNight"])}/><labelhtmlFor="departure">{form.fields.departure.error ?"Invalid" :""} departure</label><inputtype="date"id="departure"name="departure"value={form.fields.departure.value}onChange={e=>form.handleChange(e,["minOneNight"])}/></fieldset>{/* also from option 1 */}<button{...form.inputs.submit()}disabled={form.loading}> Submit</button></form>);};ReactDOM.render(<App/>,document.querySelector("#root"));
useForm(useFormParams:UserFormParams):UseForm
| name | type | description |
|---|---|---|
| name | string | Refer to one of the forms informProviderProps |
| initialState | object | SeeInitialState |
| validators | function | SeeValidators |
| onSubmit | function | SeeSubmitCallback |
| onNotify | function | SeeNotifyCallback |
An object containing the default value of every field in a form.
Example:
useForm({initialState:{email:"",name:"",address:"",age:0}})
Ifname is defined, you can refer toinitialStates.{name} informProviderProps.
Example:
//...<FormProviderinitialStates={{login:{email:"email@example.com",password:""}}}>//...const form = useForm({name:"login"})console.log(form.fields.email.value) // email@example.com
This function is invoked beforeonChange andonSubmit. The former only runs the validations for the changed fields, while the latter runs it on the whole form. For convenience, it is also returned fromuseForm.
functionvalidators(fields:object,isSubmitting:boolean):Validations
| name | type | description |
|---|---|---|
| fields | object | An object with the same shape asinitialState |
| submitting | boolean | Set totrue, when called beforehandleSubmit |
| validations | object | SeeValidations |
An object containingboolean expressions. Each input field must have at least a corresponding property in this object, but you can define custom ones as well.
Example:
{email:submitting ?isValidEmail(fields.email) :typeoffields.email==="string"}
You can also look at thelive example.
Invoked whenhandleSubmit is called and there were no validation issues.
functiononSubmit(onSubmitParams:OnSubmitParams):void
| name | type | description |
|---|---|---|
| name | string | Same asname inuseFormParams |
| fields | object | Validated fields, same shape asinitialState |
| setLoading | function | Sets the returnedloading property ofuseForm |
| notify | function | SeeNotifyCallback |
Invoked if there is a validation error when callinghandleChange orhandleSubmit. Can be manually triggered ononSubmit by callingnotify.
typeNotifyType="validationErrors"|"submitError"|"submitSuccess"functionnotify(type:NotifyType,reason:any):void
| name | type | description |
|---|---|---|
| type | string | Type of notification |
| reason | any | When type isvalidationErrors, it is a list of field names, Otherwise you set it to whatever you want. |
Example:
Look at thelive example.
| name | type | description |
|---|---|---|
| name | string | Same as inuseFormParams. |
| fields | object | SeeFieldValuesAndErrors |
| hasErrors | boolean | For convenience.true if any of the returned fields.{name}.error istrue. |
| handleChange | function | SeeChangeHandler |
| handleSubmit | function | SeeSubmitHandler |
| loading | boolean | Controlled bysetLoading inonSubmit |
| inputs | object | SeeInputPropGenerators |
| validators | function | SeeValidators |
Validated field values and their errors.
Example:
constform=useForm({initialState:{email:"email@example.com",age:-2}})console.log(form.fields)// {// email: {// value: "email@example.com",// error: false// },// age: {// value: -2,// error: truefields// }// }console.log(form.hasErrors)// true
You can call this two ways. Either pass an event as the first argument, or a partialfields object. With the latter, you can change multiple values at the same time. E.g.: resetting the form after submit, or any other reason you might have.
functionhandleChange(event:React.FormEvent,validations:string[]):voidfunctionhandleChange(fields:object,validations:string[]):void
| name | type | description |
|---|---|---|
| event | React.FormEvent | Standard event. Usingtarget.{name|value|checked} to infer the intention |
| fields | object | Pass a partialfields object, if you want to change multiple values at the same time |
| validations | string[] | Whichvalidators you would like to run. If omitted, only validators with the same event/field name will be run |
Example:
Look at thelive example.
Call to submit the form. BeforeonSubmit is invoked,validators is run for every form field. If there were any errors,notify is invoked withtype beingvalidationErrors, andreason a list of form field names.
functionhandleSubmit():void
An object, containing properties with the same name as theHTML input types, with some minor differences.
For convenience, sincedatetime-local contains a hyphen (-) character, it is also exposed asdatetimeLocal, to overcome the need of" characters, when accessing it.I.e.:
constform=useForm()form.inputs.datetimeLocal==form.inputs["datetime-local"]
In addition to the standard input types, there is aselect type also available.
Each property is a function:
functioninputType(name:string,options:InputTypeOptions):InputPropGeneratorsReturn
| name | type | description |
|---|---|---|
| name | string | The name of a field. Same as the properties ofinitialState |
| options | object | SeeInputTypeOptions |
Example:
For examples of all types, you can checkthis test suite
An optional object
| name | type | description |
|---|---|---|
| value | string | Usually, when using radio buttons, values are static. (Each button in the same group must have different values) |
| generateProps | function | Providesname,value anderror that can be used to generate additional props. Useful, if you want to avoid usingform.fields |
| formName | string | If the input type issubmit, it can be used to override the name of the form being submitted. |
Example:
constform=useForm(/*...*/)// *click on option-2*console.log(form.fields.radio.value)// option-2return(<radiogroup><input{...inputs.radio('radio',{value:'option-1'})}/><input{...inputs.radio('radio',{value:'option-2'})}/>{/*You can do it the "traditional" way also*/}<input{...inputs.radio('radio')}value='option-3'/></radiogroup>)
constform=useForm(/*...*/)return(<div><input{...form.inputs.email("emailField",{generateProps:({error})=>({className:error ?"email-error" :"email",})})}/>{/*Tip: if your custom Input component takes an error prop, you can try this: */}<Input{...form.inputs.email("emailField",{generateProps:_=>_})}/>{/* This will spread error to Input as well.*/}{/*Or here is a more complex example for a custom Input component*/}<Input{...form.inputs.email("emailField",{generateProps:({error, name})=>({ error,label:error ?`Invalid${name}` :name,placeholder:`Type${name}`})})}/></div>)
An object that can be spread on a React input like element.
| name | type | description |
|---|---|---|
| name | string | The name of a field. Must be one of the properties ininitialState |
| id | string | By default, same asname. If input type isradio, it is the same asvalue, to avoid problems in radio groups whereid would be the same |
| value | any | The value of the field |
| onChange | function | SeeonChange |
| checked | boolean | If input type ischeckbox, it is the same asvalue |
| onClick | function | If input type issubmit, it isonSubmit |
Example:
constform=useForm(/*...*/)return(<div><labelhtmlFor="emailField">E-mail</label><input{...form.inputs.email("emailField")}/>{/* This is the same as */}<inputname="emailField"id="emailField"type="email"onChange={form.handleChange}value={form.fields.emailField.value}/></div>)
functionFormProvider(formProviderProps:FormProviderProps):JSX.Element
| name | type | description |
|---|---|---|
| initialStates | object | Single place to define allinitialState for every form. SeeInitialState |
| validators | object | Single place to define allvalidators for every form. SeeValidators |
| onSubmit | function | Same asonSubmit |
| onNotify | function | Same asonNotify |
| children | ReactElement | The element you would like to wrap withFormProvider |
functiongetForms() :Forms
An object containing all the forms' current values inFormProvider. Same shape asinitialStates.
MIT
About
A React hook 🎣 for easy form handling
Topics
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.