react-simpler-forms
React Higher Order component that manages all of your forms state along with other components that make it easy to create, validate, perform search queries, and submit single or multi-step forms.
-
Zero dependencies.
-
99.9% declarative.
Requirements
-
React and React-dom 16.5.2 or greater.
-
Babel 7
Installation
npm install react-simpler-forms --save
Single-step form
;; { super thissubmit = thissubmit }; { // your response from the server, do whatever you want now! }; { //props exposed by higher order component let formProps = form: thispropsform //form state updateform: thispropsupdateform //method for updating form state ; /* disabled is also exposed by HOC and is a boolean that you can use to disable the submit button. */ let disabled = thispropsdisabled; return /* formProps.form.response will contain the success response from the server if you don't pass a success callback to the Button component. Also if you don't pass a success callback the form inputs will reset upon success. */ <div className='formSubmitSuccess'>formPropsformresponse</div> //If you get an error from the server that will be here. <div className='formSubmitError'>formPropsformerror</div> //When form is loading you can render some sort of loader. formPropsformloading && 'Loading...'
Click to view basic Input.
<div className='input-container'> <Response ...formProps for='first_name' errorClassName='input-response-error' /> <label className='label'>First Name:</label> <Input ...formProps name='first_name' className='input' focusedClassName='input-focused' errorClassName='input-error' validators= method: "notEmpty" error: "First name cannot be empty." method: "onlyLetters" error: "First name can only contain letters." method: "maxCharaters" error: "First name cannot be longer than 35 characters." /> </div>
Click to view Input that executes search query while typing.
<div className='input-container'> <Response ...formProps for='email' errorClassName='input-response-error' successClassName='input-response-success' /> <label className='label'>Email:</label> /* Query sends JSON post request after user stops typing and input has been validated. */ <Input ...formProps name='email' className='input' focusedClassName='input-focused' errorClassName='input-error' query='http://yourwebsite.com/account/checkemail' delayError=1400 type='email' validators= method: "notEmpty" error: "Email name cannot be empty." method: "validEmail" error: "Please enter a valid email." /> </div>
Click to view Inputs that are required to match.
<div className='input-container'> <Response ...formProps for='Password' errorClassName='input-response-error' matchError= matchName: 'password' error: 'Password do not match.' /> <label className='label'>Password:</label> <Input ...formProps name='Password' className='input' focusedClassName='input-focused' errorClassName='input-error' delayError=1400 match='password' validators= method: "notEmpty" error: "Password name cannot be empty." method: "validPassword" error: "Password must contain at least 8 character and 1 number." type='password' /> </div> <div className='input-container'> <Response ...formProps for='PasswordRepeat' errorClassName='input-response-error' /> <label className='label'>Password Repeat:</label> <Input ...formProps name='PasswordRepeat' className='input' focusedClassName='input-focused' errorClassName='input-error' delayError=1400 match='password' validators= method: "notEmpty" error: "Password name cannot be empty." method: "validPassword" error: "Password must contain at least 8 character and 1 number." type='password' /> </div>
Click to view textarea Input.
<div className='input-Container'> <Response ...formProps for='message' errorClassName='input-response-error' /> <label className='label'>Message:</label> <Input ...formProps name='message' className='input' focusedClassName='input-focused' errorClassName='input-error' type='textarea' scrollUp // after user finished message it will scroll to top of page. // You can use any validators here too /> </div>
Click to view radio button Inputs.
<div className='input-radio-container'> <h1>Favorite color</h1> <Response ...formProps for='favorite_color' errorClassName='input-response-error' selectError='Please select a color.' /> <div className='radio-container'> <Input ...formProps required name='favorite_color' value='red' type='radio' /> <label className='label'>Red</label> </div> <div className='radio-container'> <Input ...formProps name='favorite_color' value='green' type='radio' /> <label className='label'>Green</label> </div> <div className='radio-container'> <Input ...formProps name='favorite_color' value='blue' type='radio' /> <label className='label'>Blue</label> </div> </div>
Click to view select Inputs.
<div className='select-container'> <Response ...formProps for='state' errorClassName='input-response-error' selectError='Please select a state.' /> <label className='label'>Select State:</label> <Input ...formProps required name='state' type='select' className='select-class' oddOptionClass='odd-option-class' evenOptionClass='even-option-class' options= value: '' text: 'Please select an option' value: 'MA' text: 'Massachusetts' value: 'FL' text: 'Florida' value: 'CA' text: 'California' /> </div>
Click to view checkbox Input.
<div> <Response ...formProps for='terms' errorClassName='input-response-error' selectError='You must agree to terms' /> <div> <Input ...formProps name='terms' value='Agreed' type='checkbox' required /> <label className='label'>By checking this box you agree to the terms</label> </div> </div>
/* Submit sends a json post request to your url with all the data from the form. success={this.submit} is a function you can pass to handle the response from your server */ <Button ...formProps disabled=disabled className='button' disabledClassName='button-disabled' url='http://yourwebsite.com/signup' success=thissubmit extraData= token: usertoken /* more whatever If you need to send more data with the request body you can put that here. */ type='submit' > Submit </Button> ); };}; // Wrap your component with the Higher Order Component.App
Multi-step form
;; { let disabled ...rest = thispropsformProps return <div>
Click to view Inputs.
<div className='input-container'> <Response ...rest for='first_name' errorClassName='input-response-error' /> <label className='label'>First Name:</label> <Input ...rest name='first_name' className='input' focusedClassName='input-focused' errorClassName='input-error' validators= method: "notEmpty" error: "First name cannot be empty." method: "onlyLetters" error: "First name can only contain letters." method: "maxCharaters" error: "First name cannot be longer than 35 characters." /> </div> <div className='input-container'> <Response ...rest for='last_name' errorClassName='input-response-error' /> <label className='label'>Last Name:</label> <Input ...rest name='last_name' className='input' focusedClassName='input-focused' errorClassName='input-error' validators= method: "notEmpty" error: "Last name cannot be empty." method: "onlyLetters" error: "Last name can only contain letters." method: "maxCharaters" error: "Last name cannot be longer than 35 characters." /> </div>
<Button ...rest disabled=disabled className='button' disabledClassName='button-disabled' type='nextStep' > Next </Button> </div> ); };}; { let disabled ...rest = thispropsformProps return <div>
Click to view Input.
<div className='input-container'> <Response ...rest for='email' errorClassName='input-response-error' successClassName='input-response-success' /> <label className='label'>Email:</label> <Input ...rest name='email' className='input' focusedClassName='input-focused' errorClassName='input-error' query='http://yourwebsite.com/account/checkemail' delayError=1400 type='email' validators= method: "notEmpty" error: "Email name cannot be empty." method: "validEmail" error: "Please enter a valid email." /> </div>
<Button ...rest disabled=disabled className='button' disabledClassName='button-disabled' type='nextStep' > Next </Button> <Button ...rest className='button' type='prevStep' > previous </Button> </div> ); };}; { super thissubmit = thissubmit }; { // your response from the server, do whatever you want now! }; { let disabled ...rest = thispropsformProps return <div>
Click to view Inputs.
<div className='input-container'> <Response ...formProps for='Password' errorClassName='input-response-error' matchError= matchName: 'password' error: 'Password do not match.' /> <label className='label'>Password:</label> <Input ...formProps name='Password' className='input' focusedClassName='input-focused' errorClassName='input-error' delayError=1400 match='password' validators= method: "notEmpty" error: "Password name cannot be empty." method: "validPassword" error: "Password must contain at least 8 character and 1 number." type='password' /> </div> <div className='input-container'> <Response ...formProps for='PasswordRepeat' errorClassName='input-response-error' /> <label className='label'>Password Repeat:</label> <Input ...formProps name='PasswordRepeat' className='input' focusedClassName='input-focused' errorClassName='input-error' delayError=1400 match='password' validators= method: "notEmpty" error: "Password name cannot be empty." method: "validPassword" error: "Password must contain at least 8 character and 1 number." type='password' /> </div>
<Button ...rest disabled=disabled className='button' disabledClassName='button-disabled' url='http://yourwebsite.com/signup' success=thissubmit type='submit' > Submit </Button> <Button ...rest className='button' type='prevStep' > previous </Button> </div> ); };}; { let formProps = form: thispropsform updateform: thispropsupdateform disabled: thispropsdisabled ; let steps = <Step1 formProps=formProps /> <Step2 formProps=formProps /> <Step3 formProps=formProps /> ; return stepsformPropsformstep ; };; App
Demo page : https://mellocloud.com/react-simpler-forms
Components
SimplerForm
This is the higher order component that is used to manage all of your forms state and manage whether button is disabled or not. Simply wrap around a component where you want to create a new form.
The higher order component passes down three props
- this.props.form
- this.props.updateform
- this.props.disabled
All components require this.props.form and this.props.updateform. If you want to disable the button until validation then you can pass disabled={this.props.disabled} to the Button component.
Make sure you pass the form and updateform props using the same name
form=thispropsformformupdate=thispropsupdateform
Note: You do not need to wrap your form with the standard form tag, the SimplerForm HOC handles all post requests.
Input
This the component that is used to create all your inputs including text, email, password, textarea, select, radio, and checkbox
Prop | Description | type |
---|---|---|
form |
required this.props.form | obj |
formupdate |
required this.props.formupdate | function |
name |
The input name | string |
className |
This is the default className for the input | string |
focusedClassName |
When input is focused this classname will be applied to the input along with className | string |
errorClassName |
When there is an error with the input, this classname will be applied to the input along with className | string |
validators |
This is an array of objects that contain methods to validate the input along with an error. By default react-simpler-forms comes with the following validators 'notEmpty', 'onlyLetters', 'maxCharacters' (35 characters), 'validEmail', 'validPassword' (At least 8 characters and 1 number). You can pass custom validators as well, by putting a function in the method property, more info below under custom validators. | array |
delayError |
You can add a typing delay before showing the error. For example if someone is typing their email and you don't want to show an error right when they type that the email is invalid then you can pass a number to the delayError prop. The number is a delay in milliseconds. | number |
query |
If you want to perform a search query when the user is typing, you can pass a url and this will send a POST request with the input to your url. The post request is only made after the input is validated using the validators. | string |
match |
This is used when you need two or more inputs to match, put this prop with the same value on all the inputs that need to match | string |
type |
This is used to specify the type of input, you can pass text, email, password, textarea, radio, select or checkbox. | string |
required |
This only applies to the select, radio and checkbox input. For radio buttons just put required on the first radio button input and it will work because you can only select one input. For checkbox you have to apply required to each input you want to be required and the error gets grouped based on the checkbox name, meaning you can only display one error per category which is based on the name you pass to the input. | boolean |
options |
This only applies to the select input and its an array of options | array |
oddOptionClass |
odd classname for option selections | string |
evenOptionClass |
even classname for option selections | string |
value |
For radio and checkbox you need to pass a value prop with the input value | string |
scrollUp |
For textarea only, if you need to scroll up when you're done typing. I use this in my contact form on my website because the submit button is on top of the textarea and it scrolls down a lot if you type a long message. | boolean |
Aside from that you can pass other props that are normally used with inputs like autoComplete='off' or whatever.
Response
This the component that is used to display a response whether an error or success from your input.
Prop | Description | type |
---|---|---|
form |
required this.props.form | obj |
formupdate |
required this.props.formupdate | function |
for |
name of the input this is being applied to | string |
errorClassName |
classname if response is an error | string |
successClassName |
classname if response is a success, this is only used for queries | string |
matchError |
this is a response for inputs that required match. This is an obj that contains two properties matchName and error. matchName is the name you gave to all the inputs that need to match and error is the error if they don't | obj |
selectError |
This is used to display the error from select, radio, or checkbox | string |
Button
This the component that is used to submit your form or go to the next or previous step of a multi-step form
Prop | Description | type |
---|---|---|
form |
required this.props.form | obj |
formupdate |
required this.props.formupdate | function |
disabled |
This is not required but you can pass this.props.disabled which gets passed from the HOC and it will disable the button until all the inputs are validated. | boolean |
className |
This is the default classname for the Button | string |
disabledClassName |
classname applied to Button when disabled is true | string |
url |
if the button type is submit you can pass a url and when you submit your data will be sent using JSON post request | string |
extraData |
If you need to add extra data like a token to the request body you can put that here | obj |
type |
valid types are submit, nextStep, or prevStep | string |
More info
Custom Validators
If you want to pass custom validators you can do so like this.
{ return value === 'dan'}; <Input validators= method: thisnameisdan error: 'Name is not equal to dan' />
What I learned / Want to learn / Improved on
-
I became a lot more comfortable with higher order functions like reduce, map, filter, and every. Not mutating objects and arrays is probably one of the most important things I've ever learned.
-
As the logic got more complicated I realized it became too much to keep in my head which is why I'm starting to learn unit testing and TDD, I wish it was implemented in this project but maybe once I learn more about unit testing I'll implement it here but definitely in future projects.
-
Half way through the project I realized I didn't need any of the dependencies I was using, they made my bundle size huge and I was barely utilizing them. So I removed all my dependencies and wrote some of my own code to do the same thing. For example I was using redux to manage the state of the forms before cause I thought it was convenient but all I had to do was create a state in the HOC and some methods to update it, then pass down a function that returns a function which can run any of the other functions by passing the name and some data to update the state. This reduced my bundle size by half. I repeated the process with other dependencies. For prop-types all I did there was check some props then console.error if something is wrong. For axios I just made my own xhr function that can be reused for all my post requests.
Thank you for checking out my project.