use-models
advanced form model hooks for your functional react components. build huge, complex forms with validation using minimal boilerplate code.
Install
npm install --save use-models
Features
- form helpers
input
,checkbox
, andradio
which wire up your form elements to state with path syntax, egbook.author.firstname
. nesting supported for objects(and possibly arrays) - form validation supporting built in validators, custom validators, async validators, and ability to extend built in validators for usage across project.
- simple
state
object representing your forms state, and anerrors
object which displays when a field has an error. state properties are mirrored on the errors object. - ability to hydrate state using the
hydrate
function, useful for syncing from api or from localstorage, or whatever you need. - ability to watch fields for changes with
watch
helper method, which receivesoldValue
andnewValue
as arguments. submit
anderror
functions that create handlers for your form'sonSubmit
andonError
events. submit receives a copy ofstate
anderrors
receives a list of form errors.- no dependencies (other than react ofc)
- highly configurable/composable.
- plugin friendly, offering plenty of ways to "hook" into the library for custom functionality.
Tutorial
this tutorial will teach you the concepts of how to use the features to build your application
- setting up your state with
useModels
and wiring up an input to the state withinput
helper.
import React from 'react';import useModels from 'use-models'; { // here you can see that useModels is called with an object defining our state(and optionally validation, which will be shown in a later step) const input // input ( path, type='text' ) checkbox // checkbox( path, trueValue=true, falseValue=false ) radio // radio( path, value ) } = ; // this is example shows a login form return <div ="example"> <form> <div ="form-group"> <label>Email</label> <input /> </div> <div ="form-group"> <label>Password</label> <input /> </div> <div ="form-group-check"> <input /> <label>Remember me</label> </div> <div ="form-group with-nested"> <label>Sign up for newsletter?</label> <div ="form-group-check"> <input /> <label>Yes Sign me up!</label> </div> <div ="form-group-check"> <input /> <label>No thanks</label> </div> </div> <button ="submit">Login</button> </form> </div> ); }
- adding validation
importing the model helper:
import React from 'react';import useModelsmodel from 'use-models';//we can add the model helper for ease in defining our models. this is optional, as options can be defined as a plain object as well
using model helper:
const input // input ( path, type='text' ) checkbox // checkbox( path, trueValue=true, falseValue=false ) radio // radio( path, value )} = ;
using a plain object:
const input // input ( path, type='text' ) checkbox // checkbox( path, trueValue=true, falseValue=false ) radio // radio( path, value )} = ;
custom validator functions:
const input // input ( path, type='text' ) checkbox // checkbox( path, trueValue=true, falseValue=false ) radio // radio( path, value )} = ;
async validator example:
{ const input // input ( path, type='text' ) checkbox // checkbox( path, trueValue=true, falseValue=false ) radio // radio( path, value ) } = ; ... }
extending validators for shared re-use across your project:
import useModelsmodelextendValidators from 'use-models'; ; { const input // input ( path, type='text' ) checkbox // checkbox( path, trueValue=true, falseValue=false ) radio // radio( path, value ) } = ; ... }
you can use as many validator functions as you want. the additional arguments to model()
will accumulate:
// equivalent to { value:'', validate:['email','myCustomValidator', value=>{} ] }
- handling form submit and form submit error
{ const submit error = ; const onSubmit = ; const onError = ; return <form = => ... </form> }
- displaying form errors by reading
error
object
The state object is mirrored 1:1 to the errors object. all values in the errors object will be
false
, unless the field has an error. for example, if you have a field calledusername
, you can check if there is an error in the field by readingerrors.username
, as shown below:
{ const input submit error state errors = ; const onSubmit = ; const onError = ; return <form = => <label>Choose Username</label> <input /> errorsusername && <p ="input-error">errorsusername</p> <button ="submit">Submit</button> </form> }
- executing code when the value of a field changes using the
watch()
api
{ const input watch = ; // you can watch a field for changes, very simply // the returned function can be used to deregister the watcher const unwatch = ; return <form = => <label>Choose Username</label> <input /> errorsusername && <p ="input-error">errorsusername</p> <button ="submit">Submit</button> </form> }
- reading the
state
object
sometimes you want to read the form state and use it to render some component in your ui with the form state. you can do that with the
state
variable.
{ const input state = ; return <form> <div> <label>Please enter your age:</label> <input /> </div> <div> <p><strong>You entered: </strong> stateage </p> stateage != '' && stateage < 18 ? <p>You are not old enough to see this content</p>:<p>You are old enough to see this content</p> </div> </form> ;}
- populating the state with data using
hydrate()
you will probably face a situation where you need to load some data, whether from the network or from localstorage or even a cookie. you can do so very easily using
hydrate()
below example shows hydrating state on componentMount, which is probably the most common use case. NOTE: in addition to hydrating thestate
, you can also hydrate theerrors
object if desired, by passing a second argument. NOTE: partial updating is supported, but only for top level keys. no diffing is done at a nested level. this behavior may change in the future.
import ReactuseEffect from 'react'import useModels from 'use-models' { const hydrate state = ; ;// note: if you don't pass hydrate in as a dependency to useEffect(), the linter complains. console;// on second render you will see state populated with the values passed to hydrate. return ... ;}
- using
get()
andset()
for custom input methods or for other state features, like a loading indicator
import React from 'react'import useModels from 'use-models' { ...} { const state input get set submit = ; const onSubmit = return stateloading ? <div>'Loading...'</div> : <form => statesubmit_error && <p ="alert">Form Error: statesubmit_error </p> <h1>Register Account</h1> <div ="form-group"> <label>Your Name</label> <input /> </div> <div ="user-type-select"> <div = => Normal User </div> <div = => Admin User </div> </div> <button ="submit">Create User</button> </form>
- advanced topics: directly manipulating
state
anderrors
programatically
this feature is experimental, and you are responsible for managing the diffing of your state and error objects. failure to do so will definitely crash your ui
const getState setState getErrors setErrors = ; // you do this at your own risk!const state = ...;statefoo=1;; const errors = ...;;
API
main export:
useModels( Object options={} )
initializes the state of the component, returning helper functions for use in your component. NOTE: must be called from within a functional component.
arguments
options
- declare your default state with options for each field. example syntax{ name:'' }
or{ name: { value:'', validators:[] }}
. you can also use themodel
helper like so{ name: model(defaultValue,...validationFunctions)}
as a shorthand for declaring the value and validators for the field.
returns
- An object with helper functions
{ input, checkbox, radio, submit, error, getState, getErrors, setState, setErrors, errors, state, watch, hydrate }
other exports:
model( String name, Array<Function|String> ...validate )
- a helper for use in defining options for fields on useModels
. returns an object with shape { value, validate:[] }
extendValidators( String name, Function validator )
- adds a named validator to the built in validators, for purpose of reusing throughout your project.
useModels()
):
helpers (returned from hydrate( Object state[, Object errors] )
- allows you to hydrate your state with the data of your choice. you can optionally hydrate the errors as well.state
- this is the state object. you can use it to read/display data from the state for whatever reason you wish. (see examples)errors
- this is the errors object. you can use it to read data from the errors and display the error messages. (see examples)watch( String name, Function callback( Any newValue, Any oldValue ) )
- allows you to assign a watcher to a state field and be notified when its value changes.name
- the path of the model to watch. nesting is supportedcallback
- the callback function to execute when the model changes. it will receivenewValue
andoldValue
as arguments.
input( String name, String type='text' )
- for use withinput
,select
,textarea
and other components. returnsprops
for use on inputs.name
- the path of the model. nesting is supported. examples of valid paths:firstname
orbook.author.firstname
orpost.comments.0.text
.type
- the type attribute for the input. defaults totext
checkbox( String name,Any truevalue=true,Any falsevalue=false )
- to be used for checkbox components, whether native or custom. returnsprops
for use on inputs.name
- see description underinput
truevalue
- the value for the field if checkbox is checked. defaults totrue
falsevalue
- the value for the field if checkbox is unchecked. defaults tofalse
radio( String name, Any value=null )
- for use with radio components, whether native or custom. value is the value to assign to the state if the radio is checked. returnsprops
for use on inputs.name
- see description underinput
value
- the value of the field if the checkbox is checked.
get( String name )
- can be used to read the value of a given model.set( String name, Any value, Boolean validate=true, Boolean watchers=true )
- can be used to directly manipulate a given model.name
- the path of the model to update. nesting is supported.value
- the value to set for the model.validate
- whether or not to run validators on the new value. defaults totrue
.watchers
- whether or not to run watchers on the new value. defaults totrue
.
submit( Function callback( state ) )
- given a callback, this method returns anonSubmit
handler for your form. the passedcallback
will be called with thestate
object.callback
- a function to be called when the form is submitted.
error( Function callback( errors, state) )
- given a callback, this returns anonError
handler for your form. if any native validation or custom validation errors occurs, you will receive that info here.callback
- a function to be called when an error occurs during form submit.
getState
- retrieves thestate
object programatically.setState( Object newState )
- allows you to programatically manipulate the state object.getErrors
- retrieves theerrors
object programatically.setErrors
- allows you to programatically manipulate the errors object.
License
MIT © r3wt