npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-simpler-forms

v1.0.2

Published

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.

Downloads

5

Readme

react-simpler-forms

npm version

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

  1. React and React-dom 16.5.2 or greater.

  2. Babel 7

Installation

npm install react-simpler-forms --save

Single-step form

import React, { Component } from 'react';
import { SimplerForm, Input, Response, Button } from 'react-simpler-forms';

class App extends Component {

  constructor() {

    super()

    this.submit = this.submit.bind(this)
  };

  submit(response) {

    // your response from the server, do whatever you want now!
  };

  render () {

    //props exposed by higher order component
    let formProps = {
      form: this.props.form, //form state
      updateform: this.props.updateform //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 = this.props.disabled;

    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'>{formProps.form.response}</div> 
      //If you get an error from the server that will be here.
      <div className='formSubmitError'>{formProps.form.error}</div>
      //When form is loading you can render some sort of loader.
      {formProps.form.loading && 'Loading...'}
    <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>
    <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>
    <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>
    <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>
    <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>
   <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>
    <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={this.submit}
      extraData={{
        token: user.token,
        /* 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.
export default SimplerForm(App)

Multi-step form

import React, { Component } from 'react';
import { SimplerForm, Input, Response, Button } from 'react-simpler-forms';

class Step1 extends Component {
  
  render() {

    let {disabled, ...rest} = this.props.formProps

    return (
      <div>
    <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>
    );
  };
};

class Step2 extends Component {
  
  render() {

    let {disabled, ...rest} = this.props.formProps

    return (
      <div>
    <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>
    );
  };
};

class Step3 extends Component {

  constructor() {

    super()

    this.submit = this.submit.bind(this)
  };

  submit(response) {

    // your response from the server, do whatever you want now!
  };
  
  render() {

    let {disabled, ...rest} = this.props.formProps

    return (
      <div>
    <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={this.submit}
        type='submit'
        >
        Submit
      </Button>

      <Button
        {...rest}
        className='button'
        type='prevStep'
      >
        previous
      </Button>
    </div>
    );
  };
};

class App extends Component {

  render () {

    let formProps = {
      form: this.props.form,
      updateform: this.props.updateform,
      disabled: this.props.disabled
    };

    let steps = [
      <Step1 formProps={formProps} />,
      <Step2 formProps={formProps} />,
      <Step3 formProps={formProps} />
    ];

    return (

      {steps[formProps.form.step]}
    );
  };
};

export default SimplerForm(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

  1. this.props.form
  2. this.props.updateform
  3. 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={this.props.form}
formupdate={this.props.updateform}

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.


nameisdan(value) {
  
  return value === 'dan'
};

<Input
  validators={[
    {method: this.nameisdan, error: 'Name is not equal to dan'}
  ]}
/>

What I learned / Want to learn / Improved on

  1. 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.

  2. 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.

  3. 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.