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-delicious-form

v1.1.7

Published

A delicious framework for working with forms in react and react-native

Downloads

11

Readme

react-delicious-form

A new react library created to make working with forms, like, totally delicious.

Motivation for yet another Form library

The goal of react-delicious-form is to provide a flexible way to create forms in React and does not tie you to any state management library. It also does not provide any components out of the box (although it does provide small number of simple validation helper functions).

The API is simple and the props that it decorates your component with should be straight forward to use. The default export of react-delicious-form is a single higher-order-component which should provide you with everything you'll need in order to make an awesome form. However, this library tries not to use any magic to accomplish this; input components will not magically appear - it is left up to you to build your own Input components. Form submission is not prevented by default, for example - you will have to decide under what circumstances it is okay to submit your form, display validation messages or otherwise show the user what state your form is currently in. This allows for:

  1. a natural way to build your forms
  2. the opportunity to move form logic out of your component and into a pure .js file (with no jsx)
  3. an easy and highly customizable form components, the implementation of which is left up to you.

Bugs

This is a new library so if you bump into any bugs then please report them here. If you have any feature requests feel free to add them!

Installation

npm install --save react-delicious-form

In the wild

Basic Usage

// MyFormComponent.js

import withExampleForm from './exampleForm'
 // The creation of an Input component is left up to you - for now. 
 // You'll find an example of how you might create an Input component that 
 // makes use of the props created by the withForm HOC
import { Input } from 'shared/components'

class MyFormComponent extends Component {

  render() {
    const { fields, form } = this.props
    return (
      <form onSubmit={form.onSubmit}>
        <Input {...fields.firstName} />
        <input type="submit" value="Save" />
      </div>
    )
  }
}

// wrap your form component and export.
export default withExampleForm(MyFormComponent)
// withExampleForm.js

const withExampleForm = withForm({

  fields: { // define your fields
    firstName: {
      props: { // available on your component via this.props.fields.firstName.props
        label: 'First name',
        placeholder: 'Enter your first name',
        ...
      }
    }
    ...
  },

  formHasFinishedLoadingWhen: (props) => !props.user.isFetching && props.refData.hasLoaded,

  mapPropsToFields: (props) => ({ // called once formHasFinishedLoadingWhen returns true
    firstName: props.user.firstName,
    ...
  }),

  formIsSubmittingWhen: (props) => props.user.submitting,

  onSubmit: (formItem, props, context) => { // available on your component via this.props.form.submit
    if (props.someResourceId)
      props.updateSomeResource(props.someResourceId, formItem)
    else
      props.createSomeResource(formItem)
  },

  mapPropsToErrors: (props) => ({
    firstName: props.errors.firstName, // must be an array of strings for each field
    ...
  })

})

export default withExampleForm

API

withForm (higher-order-component)

  • arguments:
    • FormDefinition object
      • fields
      • formHasFinishedLoadingWhen
      • mapPropsToFields
      • onSubmit
      • formIsSubmittingWhen
      • mapPropsToErrors
  • returns a component decorated with form and fields props

Configuration

Overview of FormDefinition

| Property | Type | Description | | ------------- |-------------| -----| | fields | object | The field definitions for this form. Used to specify props and validation for each field. Click here to see what each field definition is comprised of. | | formHasFinishedLoadingWhen(props) | function | A function which accepts all incoming props and returns a boolean indicating whether the form has finished loading. mapPropsToFields will not be called untils formHasFinishedLoadingWhen function returns true. Specifies when all the data has finished loading for this form and hence when initial values can be mapped. The return value of formHasFinishedLoadingWhen affects form.status - while the form is loading form.status === 'loading' NB: The form will be disabled until this function returns true. | | mapPropsToFields(props) | function | Maps incoming props to the fields definied by fields. Must return an object whose keys match the keys defined in fields. Unrecognized keys will not be mapped to any field. This function will only be called once formHasFinishedLoadingWhen returns true. | | onSubmit(formValue, props, context) | function | Maps incoming props to the fields definied by fields. Must return an object whose keys match the keys defined in fields. Unrecognized keys will not be mapped to any field. This function will only be called once formHasFinishedLoadingWhen returns true. | | formIsSubmittingWhen(props) | function | The field definitions for this form. Used to specify props and validation for each field. | | mapPropsToErrors(props) | function | Maps incoming props to errors. This is intended to map server-side validation to the fields on the form. Must return an object whose keys match the keys defined in fields. Unrecognized keys will not be mapped to any field, however all values will be available in your component in this.props.form.errors which is useful for displaying errors that do not relate to any field in particular. The value of each key must be a simple string[] containing error messages for that field. |

FormDefinition.fields

For every FieldDefinition you can supply 3 optional properties.

  1. props - there are 2 possible ways provide these:
  • As satic values
firstName: {
  props: {
    label: 'First name',
    style: { color: '#000' }
  }
}

// `fields.firstName.props` contains the keys `label` and `style`
  • Using a function
// A function that accepts incoming props and returns an object which contains the props for this field.
// Useful if you need to compute these values based on some props being passed into your component
firstName: {
  props: (props) => ({
    label: props.getIntl('user.country.label'),
    placeholder: props.getIntl('user.country.placeholder'),
    className: props.currentModule.theme.input,
    options: props.countries
  })
}

// `fields.country.props` contains the keys `label`, `placeholder`, `className` and `options`
  1. validators - a list of validator functions that are used to determine whether the field isValid or not. There are a small number of validator functions that come with react-delicious-form. If message is provided a default message is given.
import withform, { email, isRequired, minLength, maxLength } from 'react-delicious-form'
...
firstName: {
  ...
  validators: [
    isRequired(),
    minLength(3, 'First name must be at least 3 characters')
  ]
}
...
  • Similar to the way you can provide a function to compute props, the same can be done for validators:
firstName: {
  ...
  validators: (props) => ([
    isRequired(props.getIntl('firstName.validation.required')),
    minLength(props.minNameLength, props.getIntl('firstName.validation.minLength', props.minNameLength))
  ])
}
  • It is also possible to define your own validators. Each validator is a function which can accept up to 3 arguments in the following order field, allFields, props.Each validator must return a ValidationResult object containing an isValid value and a message (if isValid === false this is the message that will be presented)

// passwordValidators.js
const checkPasswordStrength = (field, allFields, props) => {
  const isValid = SOME_COMPLEX_REG_EX.test(field.value)
  return {
    isValid,
    message: isValid ? undefined : 'Your password isnt strong enough'
  }
}

const checkPasswordsMatch = (field, allFields, props) => {
  const isValid = field.value === allFields.password.value
  return {
    isValid,
    message: isValid ? undefined : 'Passwords do not match'
  }
}

// LoginForm.js
...
fields: {
  password: {
    props: {
      label: 'Password',
      type: 'password'
    },
    validators: [
      isRequired('Password is required')
      checkPasswordStrength
    ]
  },
  confirmPassword: {
    props: {
      label: 'Confirm password',
      type: 'password'
     },
     validators: [
      checkPasswordsMatch
    ]
  }
}
  1. initialValue - an optional value to be used as the initial value for this field
  • As with props and validators this can either be a static value or a function that maps incoming props to this field.Note this takes priority over any value supplied for this field in mapPropsToFields. As with mapPropsToFields, if initial value is a function it will only be used to set initialValue once formHasFinishedLoadingWhen returns true
countryOfBirth: {
  props: {
    label: 'Select $#*! hole country',
    options: [
      'South Africa',
      'Zimbabwe',
      'Nambia'
    ]
  },
  initialValue: (props) => props.user.countryOfBirth
  // alternatively you can use a static value
  // initialValue: 'South Africa'
},
...

FormDefinition.formHasFinishedLoadingWhen(props)

  • formHasFinishedLoadingWhen is an optional function which tells your form when it is ready to receive props and map them to the form fields.If this function is supplied you will not be able update any form values until this formHasFinishedLoadingWhen returns true. If this function is not supplied the form will be will be considered loaded by default.
...
formHasFinishedLoadingWhen: (props) => props.formType === 'create' || !props.fetching
...

FormDefinition.mapPropsToFields(props)

  • mapPropsToFields is a function that should return a plain object whose keys match those defined by FormDefinition.fields. If this function is not supplied default values will be assigned to each field
...
mapPropsToFields: (props) => {
  if(props.formType === 'edit') {
    return props.user
  }
  // no need to return any values for the form is there are none
}
...

FormDefinition.formIsSubmittingWhen(props)

  • onSubmit is a function that maps incoming props to a boolean values that tells the form when it in 'submitting' state. This is useful to disable buttons on your form, or to show a loader of some kind to your users.
...
formIsSubmittingWhen: (props) => props.isSubmitting
...

FormDefinition.onSubmit(formValue, props, context)

  • onSubmit is a function that accepts 3 arguments.
    • formValue - the current value of the form
    • props - all props passed to your component from its parent
    • context - a wild card value which can be passed to this function from your component. This is useful if you have some local state in your form that needs to be available when submitting your form.

See contrived example below:

...
onSubmit: (formValue, props, context) => {
  if(context.isRegistration) {
    props.createAccount(formValue)
  } else {
    props.login(formValue)
  }
}
...

// this can then be called in your component like so:

class AuthForm extends Component {
  state = { isRegistration: true }

  ...
  submit = () => {
    const { onSubmit } = this.props.form
    onSubmit(this.state)
  }

  render() {
    return (
      <div className="login-form">
  
        ...
  
        <input type="button" onClick={this.submit} />
      </div>
    )
  }
}

FormDefinition.mapPropsToErrors

Maps incoming props to errors. This is intended to map server-side validation to the fields on the form. Must return an object whose keys match the keys defined in fields. Unrecognized keys will not be mapped to any field, however all values will be available in your component in this.props.form.errors which is useful for displaying errors that do not relate to any field in particular. The value of each key must be a simple string[] containing error messages for that field. If any errors can be mapped they can be accessed via this.props.fields[someCoolFieldName].errors.Note that mapPropsToErrors does not store these errors in any state, it simply maps them to your fields, therefore you are responsible for clearing our any error messages from whatever they are stored. Also note that these errors will be displayed regardless of whether the user has attempted submitting the form or not. Errors will be mapped as soon as they are found on props.

...
mapPropsToErrors: (props) => ({
    ...props.serverErrors,
    firstName: props.serverErrors.fName, // must be an array of strings for each field
})
...

Example - Defining form fields

// UserForm.js
import withForm, { isRequired, minLength } from 'react-delicious-form'
import { Input } from 'shared/components'

export default withForm({
  fields: { // FieldDefinitions
    firstName: {
      props: {
        label: 'First name',
        style: { color: '#000' }
      },
      validators: [
        isRequired('First name is required'),
        minLength(3, 'First name must be at least 3 characters')
      ],
      initialValue: ''
    }
    ...
  },
  ...
})(({ form, fields }) => (
  <form onSubmit={form.onSubmit}>

    // Flatten each field can be useful for when making use of PureComponent
    <Input
      {...fields.firstName.handlers}
      {...fields.firstName.state}
      {...fields.firstName.props}
    />

    <input 
      type="submit" 
      value="Save User" 
      disabled={!form.validation.isValid || form.status === 'submitting'} 
    />

  </form>
))

Using the fields and form props in your component

These are the only two objects that the withForm hoc adds to your component. Together they contain the functions and state that you'll need to work with forms. These props can be accessed in your component as follows:

const { fields, form } = this.props // for class components
const { fields, form } = props // for stateless components

Working with this.props.fields

fields is a simple object, the keys of which correspond to the fields: config object that you defined in the withForm hoc.The value of each field is defined below:

This contains the state of a field which is made up of following values

| Property | Type | Description | | -------- | ---- | ---------- | | name | string | The key - whatever you've named it. i.e.console.log(fields.firstName.state.name) // firstName | value | any | The current value of this field | originalValue | any | The initial value of this field.Equal to '' or whatever mapPropsToFields returned for this field | touched | boolean | true if the value of this field has changed at least once. Not the same as isDirty.A field will still be touched even if value is changed back to originalValue | didBlur | boolean | true if the input that controls this field has gained and lost focus at least once | isDirty | boolean | true whenever value !== originalvalue, otherwise false | isValid | boolean | true if all validation defined in validators passes, otherwise false | messages | string[]| An array of strings which contains all the validation messages for this field. messages will be empty if isValid === 'true'

  • The props property

This simply contains the props that you defined for the field in the FieldDefinition wish to {...spread} on to your input and is a convient way to define the props any given input field.

A note about using the {...} spread operator:

Be careful not to spread props onto an HTML input without checking that all the props passed to it belong on said element, otherwise React is likely to give you a warning. It is recommended that you create your own Input components that know what to do with the props that are being passed to them. You can find an example of this further down on this page.

This contains two important functions:

  • onChange(e)
  • onBlur(e)

These handlers are crucial and should should be given to your Input component so that it knows how and when to update the fields state.

A note about handlers:

Both onChange and onBlur must be passed the event parameter since state for every field is changed using event.currentTarget.name. If you wish to update the fields value manually you will have to use the updateField or bulkUpdateFields functions which are made available on the form prop

Working with this.props.form

The second prop that is made available to your component is the form object:

| Property | Type | Description | | --- | --- | --- | | validation | object | Contains the validation state of the entire form | | onSubmit | function | The function used to to submit your form. It accepts a single optional parameter which is passed to onSubmit (3rd argument) on your FormDefinition config when defining your form. NB Do not get confused by onSubmit (a function which is a property of the FormDefinition object), and form.onSubmit(context) function which is ultimately made available to your component via the form prop. | | updateField | function | A function used to update a single field. | bulkUpdateFields | function | A function used to update multiple fields simultaneously. | | status | string | 'loading', 'submitting', 'touched', 'clean' | | isDirty | boolean | true if any one or more field's isDirty flag is also true | | value | object | An object containing current value of the form | | errors | string[] | A flattened list of errors based on the values of the object return by mapPropsToErrors | | submitCount | number | A number indicating the number of times the onSubmit method has been called| |hasSubmitted| **boolean** | A value indicating whether the user has attempted to submit this form at least once.hasSubmittedwill betrueifsubmitCount > 0, otherwise false` |

Example - updateField, bulkUpdateFields

// updates a single field
form.updateField('firstName', 'Munk')

// update multiple fields at the same time 
form.bulkUpdateFields({
  firstName: 'Munk',
  lastName: 'Jones'
})

Example - creating inputs for your form

Below is an example of what an Input and a FormSubmit component might look like. You can use the state of a field to determine when and how to display validation messages. Use form state to alter what class is applied to a button, etc. Feel free to copy paste!

// Input.js

export default Input = ({
  reff,
  label = '',
  name,
  placeholder,
  onChange = () => { },
  isDirty,
  value,
  disabled = false,
  className,
  errors = [],
  isValid = true,
  messages,
  touched = false,
  didBlur = false,
  onBlur = () => false,
  showMessages = messages.length > 0 && didBlur && touched,
  originalValue,
  required,
  ...props
}) => (
    <div className="form-group">
      <label
        htmlFor={name}
        className="control-label"
      >
        {label}
      </label>
      <input
        name={name}
        className="form-control"
        ref={reff}
        disabled={disabled}
        placeholder={placeholder}
        onChange={onChange}
        value={value}
        aria-describedby={name}
        onBlur={onBlur}
        {...props}
      />
      {showMessages && (
        <span id={name} style={{ fontSize: 10, color: 'red' }}>
          {messages[0]}
        </span>
      )}
    </div>
  )
// FormSubmit.js
const defaultButtonText = {
  clean: 'Saved',
  loading: 'Loading',
  touched: 'Save',
  submitting: 'Saving'
}

class FormSubmit extends PureComponent {

  render() {

    const {
      onClick,
      className,
      disabled = false,
      formStatus = 'touched',
      buttonText = defaultButtonText, 
      ...props } = this.props

    return (
      <button
        onClick={onClick}
        className={className}
        disabled={disabled}
        {...props}
      >
        {buttonText[formStatus]}
      </button>
    )
  }
}

export default FormSubmit
// Form.js
import { Input, FormSubmit } from 'src/shared/components' // or what have you

class MyForm extends Component {

  render() {
    const { fields, form } = this.props
    return (
      ...
      <Input
        {...fields.registrationNumber.handlers}
        {...fields.registrationNumber.state}
        {...fields.registrationNumber.props}
      />
      <FormSubmit
        onClick={form.submit}
        formStatus={form.status}
      />
      ...
    )
  }
}

export withForm({
  ...
})(MyForm)