Code Monkey home page Code Monkey logo

react-advanced-form's Introduction

Package version Build status Vulnerabilities Dependencies status DevDepenencies status Greenkeeper badge

React Advanced Form

React Advanced Form

React Advanced Form is a library for tailoring real-world forms in React with pleasure and ease.


Features

Expectations shift

Trust and expect a form to do more than just rendering the fields. Our features are designed to handle cumbersome use cases with clean and performant code

Immutable

Each field interaction or update is a pure function that produces the next state of a field.

React Advanced Form is field-centric. That means you define flexible fields composites and reuse them throughout the application. Reflect even the most granular field state changes in the UI to achieve the outmost user experience.

import React from 'react'
import { createField, fieldPresets } from 'react-advanced-form'

const Input = ({ fieldState, fieldProps }) => {
  const { valid, invalid } = fieldState

  const classNames = [valid && 'has-success', invalid && 'has-error'].filter(
    Boolean,
  )

  return <input {...fieldProps} className={classNames.join(' ')} />
}

export default createField(fieldPresets.input)(Input)

Clean and fast

Develop production-ready forms in a speed of a prototype.

// This is not a diminished example, this is a finite form
<Form action={this.registerUser}>
  <Input name="username" required />
  <Input name="password" type="password" required />
</Form>

Select fields and declare validation rules using resolver functions. Utilize the order and priority of their execution to craft validation logic of any complexity.

export default {
  type: {
    password: {
      capitalLetter: ({ value }) => /[A-Z]/.test(value),
      oneNumber: ({ value }) => /[0-9]/.test(value),
    },
  },
  name: {
    confirmPassword: ({ get, value }) => {
      /**
       * The "confirmPassword" field will be re-validated whenever
       * the "value" prop of "userPassword" field updates.
       */
      return value === get(['userPassword', 'value'])
    },
  },
}

Each validation resolver can access respective field's value, fieldProps, and the form as the parameters. It can also reference other field's state via the get function, which creates a props subscription to re-evaluate the respective validation rule in real time.

Say goodbye to crowded validate functions, welcome clean validation schema!

How much effort would it take you to make one field required based on another field(s)? Yes, the correct answer isβ€”one line of code:

<Input
  name="firstName"
  required={({ get }) => !!get(['lastName', 'value'])} />
<Input
  name="lastName"
  required={({ get }) => !!get(['firstName', 'value'])} />

Get as many data from the sibling fields as needed, and build your logic around that. Rely on reactive programming that will re-evaluate a resolver function whenever the referenced field props update.

Control the serialized data structure on the layout level by grouping the fields. Take advantage of nested and split groups.

<Input name="companyName" value="Google" />

<Field.Group name="billingAddress">
  <Input name="firstName" value="John" />
  <Input name="lastName" value="Maverick" />
</Field.Group>

<Checkbox name="termsAndConditions" checked />

<Field.Group name="deliveryAddress">
  <Input name="firstName" value="Catheline" />
  <Input name="lastName" value="McCoy" />
</Field.Group>

The form above serializes into the following JSON:

{
  "companyName": "Google",
  "billingAddress": {
    "firstName": "John",
    "lastName": "Maverick"
  },
  "termsAndConditions": true,
  "deliveryAddress": {
    "firstName": "Catheline",
    "lastName": "McCoy"
  }
}

Third-party integration

React Advanced Form can be used with any third-party fields library by using powerful createField API. It also allows to create custom fields from literally any component.


Getting started

Install

npm install react-advanced-form --save

Make sure to have React (15.0+) installed in your project.

Guidelines

Starting with something new may appear challenging. We have prepared step-by-step instructions on how to Get started with React Advanced Form to make the adoption process clear and fast.


Materials


Browser support

Chrome Firefox Safari iOS Safari Edge Internet Explorer
65+ 57+ 9+ 8+ 41+ 11*

* There is no official support for Internet Explorer. Consider educating the web and deprecating legacy browsers.


Live examples


Contributing

Any of your contributions are highly appreciated. Please read through the Contribution guidelines beforehand. Development isn't the only way to support, there are many more.

react-advanced-form's People

Contributors

dandelionadia avatar greenkeeper[bot] avatar jetpack3331 avatar kettanaito avatar ludovitkapusta avatar redraushan avatar vidlec avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-advanced-form's Issues

Add useful warning messages

What

Need to add useful warning messages for developers.

Why

Because this makes a good library great.

Scenarios

  1. Mounting a field with a name which is already registered in the Form.
  2. Calling Form.serialize() manually results into warning that you need to make sure to validate the form first, so that the serialized Object is ready to be used.

Allow "Field" to be controlled by the end developer

What

Need to think of a proper way how to allow the end developer make certain properties on Field controlled.

Why

You expected

<Field.Input value="My value" disabled={ isLoading } />

to work directly.

How

  1. Let's agree that the Field is always controlled by the context. We need to pick a single source of truth, and internal context updating mechanism is more reliable than custom props passed in.
  2. Handle the updates of custom controlled props through componentWillRecieveProps to remap them in the context as well?

Feature: Extend validation rules

What

Need to add an ability to extend the application-wide validation rules when providing a form-specific validation rules.

Why

This would grant more flexible control over the validation rules, reducing the amount of code you need to repeat (potentially).

How

First that comes to my mind is to have some extend: true property on the root level of the validation rules Object. This could tell the form that it needs to merge the rules, not override them.

Introduce build pipeline

What

Introduce efficient build pipeline.

Why

Release 1.0 is near! The repository should be ready for publishing and installation.

How

  • Introduce minification (BabelMinify) to the webpack bundle.
  • Make webpack bundle env-dependant
  • Make react and immutable peer dependencies

Refine Field rendering

What

Need to refine the approach for Field rendering.

Why

Currently, Field is not rendered unless it's registered in the context of the Form. While this is the safest thing to do, it results into a splash of unrendered component. This makes the form look and feel slow.

How

  • Find another way of smart rendering
  • Ensure contextProps server as the single source of truth (no more props.prop || contextProps.prop madness)
  • Write a const sourceOfTruth = this.contextProps || this.props at Field.render()

Adopt strong types (TypeScript)

What

Need to make the library strongly typed. TypeScript is preferable.

Why

Strong types guarantee seamless types usages, eradicate bugs and help during the development.

Introduce "Field.Group"

What

Introduce <Field.Group> from the previous versions of the Form.

Why

It is potentially useful for multi-form layouts like we have.

How

Look up the previous local versions.

Unit tests (Enzyme)

What

Need to write tests for the components.

Why

Testing is a must.

How

  1. Use Enzyme (+ mocha)
  2. Introduce at least basic testing scenarios (will be listed below).

Test scenarios

Below are the test scenarios for Enzyme tests.

Basics

  • Form grabs the rules and messages from FormProvider properly
  • Form can be serialized manually
  • Form can be validated manually
  • Custom field component is registered properly within the parent form
  • Fields wrapped in Field.Group are registered properly within the parent form
  • Custom field components wrapped in Field.Group are registered properly within the parent form

Validation

  • Empty optional field without any rules.
  • Filled optional field with rule.
  • Empty required field without any rules.
  • Filled required field without any rules.
  • Filled required field with rules.
  • The same for custom styled fields

Test autocomplete by browsers

What

I think autofill done by browsers cause fields to fire unexpected field cycle events (onFocus, onBlur).

Why

It seems that autofilling is achieved by physically touching the field, pasting the value, and blurring out. Moreover, it seems that the last autofilled field remains focused unless the user clicks somewhere else. This causes the last autofilled field to look not validated (no blur happened).

How

Needs investigation.

"fieldProps" passed from "connectField" duplicate known props

What

fieldProps passed to the custom styled inputs contain the props which are already in the WrappedComponent's this.props. This creates the duplicates and makes props tree hard to read.

Expected behavior

  • Think of an efficient way how to handle this. Diff props?

Feature: "Field.Checkbox"

What

Need to introduce a basic Field.Checkbox component.

Why

In order to properly handle state transitions for checkbox components.

Expose "Form.serialize()"

What

Need to be able to serialize the Form by its reference.

Why

This is useful in case of a non-conventional form submit (multiple forms, custom submit logic).

How

const serialized = formRef.serialize();

Ensure "asyncRule" function can have custom Promise resolver

What

Need to ensure that asyncRule function can have custom Promise resolver.

Why

It's most likely developers would have their own resolve logic based on the WS response. It doesn't necessarily means that the request should resolve. It may have additional logic (i.e. depend on statusCode)

How

This is one of the expected usages of asyncRule:

<MyField asyncRule={() => fetch('https://backend.dev/ws', { ... }).then((payload) => {
  return (payload.statusCode !== 'ERROR')
}) } />

This reads: the field is considered valid unless payload.statusCode equals ERROR. In case the request throws, this should be handled automatically as invalid state of the field.

Bug: onChange validation doesn't fire properly

What

The debounced sync validation doesn't always fire properly after onChange event.

Why

There must be something wrong with the immediate argument of the debounce, or something else.

Steps to reproduce

  1. Nicely reproducible on our Registration form of the project.

Form should have a "onInvalid" callback

What

Form should have a onInvalid callback.

Why

There should be a dedicated callback for scenarios when validation of the form fails.

How

onInvalid?: ({ fields, invalidFields, formProps }) => void

The key part of the callback is automatically composed invalidFields: Array<fieldProps>. Those are sorted by the order of the field in the form (from top to bottom), and can be used by end developer for various UI logic (i.e. scrolling to the first invalid field).

Write better stories

What

Need to write better stories for storybook.

Why

Stories now serve as integration tests. Having good, real-world stories, is a key for testing the library.

Stories examples

  1. Registration form
  2. Fields with dynamic props example (RFQ?)

Introduce "Field.props.initialValue"

What

Need to have an ability to pre-fill the field without making it fully controllable on the end-side.

Why

Right now passing Field.props.value will indeed pre-fill the field, but then you ought to control its value by yourself. This is not always a desired behavior.

How

  1. Introduce new initialValue prop on Field
  2. Make it propagate properly through optional connectField (just pass through)
  3. Set the value in the Field.componentWillMount based on initialValue

Write a documentation

What

Need to write a documentation giving an overview of the available features and examples how to use them.

Why

Documentation is a vital part of any library, especially on such complex matter as forms.

Bug: Controlled "disabled" conflicts with validation

What

Native validation makes the field disabled. When there's also a controlled disabled happening, there's a situation that native validation sets field to disabled after the controlled disabled is set to false. This causes the field to be disabled forever.

How

  1. Remove enforced disabled: true during the validation. End developer has validating prop to make the field disabled if he needs to connect it with validation status.

Feature: Introduce "Radio" field

What

Need to introduce a Field.Radio component.

Why

Although <Field.Input type="radio" /> would look proper, the onChange handler of default text input and radio input would differ. Radio inputs essentially have the same name, and are selected based on their value. This is not how Field.Input should behave, whereas it has a unique name, and its value is updated correspondingly upon onChange.

How

  • Introduce new component extending generic Field
  • Write proper renderField method
  • Add any additional handlers in order for Field.Radio to work and serialize properly

Field not validated with "value" pre-defined

What

When the field's value prop is pre-defined, it doesn't get validated.

Expected behavior

I fieldProps.value is set, should call Form.validateField({ fieldProps }); to trigger respective context properties updates for the field.

Unregister field from the form's state

What

Need to call some unregister method (similar to mapFieldToState) when unmounting the component.

Why

Right now the internal references and props of the field still reside after the field has been unmounted. You would never what this behavior on purpose. Field's reference in the state should be removed, and state updated.

"Condition" doesn't react to "when" properly

What

Condition component sometimes reacts weirdly to its condition.

Steps to reproduce

  1. Open "Conditional" story.
  2. Enter "admin" into "username" field.
  3. See "password" appears.
  4. Enter something else than admin and click somewhat to the top of the field.
  5. See "username" field invalid, while "password" field remains visible.

Expected behavior

  • "password" field corresponds to its condition always, and properly.

Refine and refactor existing code

What

Need to run through the implemented code and refactor it.

Why

The thing now may look somewhat messy. It's been a rapid development without proper test, what to expect.

Use RxJS to update reactive (dynamic) props

What

Need to have the fields with dynamic props to update the latter once their dependency changes in real time.

Why

Having the obsolete state of the field results into bad UX when using the forms.

Test scenario

Consider this form:

<Form>
  <Field.Input name="username" />
  <Field.Input name="password" required={({ fields }) => fields.username && !!fields.username.value} />
</Form>

The above reads as "password field is required once username field has value". This works gracefully, however:

  1. Enter some value into "username".
  2. Try to submit the form.
  3. See the error under "password", since its dynamic required resolved to true (username has value).
  4. Remove the value from "username".
  5. See "password" still showing the "required" error.

Expected behavior

  1. "password" should immediately update its dynamic prop based on the changes of its dependants.
  2. The error message beneath "password" should disappear after the dynamic prop (required) resolve to false. This will be achieved automatically once # 1 is done.

How

  • It would be nice to determine the dynamic prop's dependancies (fields) and update it once the latter are changed. However, I don't see a way for this to work since dynamic props are functions returning an expected prop value. There is no sane way of determining what would happen in the uncontrolled function.
  • Therefore, makes sense to update required props on Form.updateField, as a callback of the method.

Roadmap

  • Create a unified interface for field's props change subscriptions
  • Dispatch props changed event when the field's props change (specific props monitoring or props diff)
  • Allow to subscribe to multiple props changes of multiple fields
  • Resolve reactive props upon the subscriber field registration
  • Create efficient rxUtils to manage observers and subscriptions
  • Resolve reactive props when the subscribed field is not yet registered (i.e. rendered conditionally). Delegated subscriptions?
  • Subscribe to the changes of props important for the field's record (keep field record in sync with the changing dependent props)
  • Write integration tests

Use "withImmutable" for Immutable argument properties in callbacks

What

Need to allow the usage of Immutable instances in callback argument properties.

Why

  • ImmutableJS is a required peer dependency. There is a high chance project is using Immutable already, or going to do so.
  • Conversion to plain JavaScript instances takes resources and time. Unnecessary when Immutable workflow is inherited in the project.

How

  1. Conversion to mutable still happens by default. RAF is never enforcing to use any technology.
  2. Introduce the prop controlling this behavior (on a FormProvider level):
<FormProvider withImmutable />
  1. Introduce a helper function to keep conditional logic at one place:
function emitMutable(immutableInstance) {
  return withImmutable ? immutableInstance : immutableInstance.toJS();
}
  1. Find a way for the helper function to access this.context.withImmutable automatically, without the need to drag it around to each util method.

Any button triggers form submit

What

It seems that any <button> inside the <Form> triggers a form submit.

Why

Maybe something wrong with how the form is submitted. Needs investigation.

updateValidState called after field onChange

What

fieldUtils.updateValidState() is called after onChange of the field.

Test scenario

Have the following field:

<MyInput name="someField" rule={/^\d+$/} value="Preset value" />

Erase the field's value in the UI.
Enter "hh".
See the check tick appearing, stating that the field is valid.

Expected behavior

  • updateValidState() is not called on onChange, but within onBlur

Enhancer: Field mask

What

Need to introduce field format.

Why

It's quite comfortable to have the value entered formatted in the pre-defined way.

How

Field format/template will enforce the provided value to be in the provided format/template. Formatting works onChange and on paste.

Example

Imagine there's a cardNumber field for entering your credit card's number. It's great to have the value be 4 digits separated with spaces while the user types.

<Field.Input
  name="cardNumber"
  format="#### #### #### ####"
  required />

This will enforce the given format while user types.

Specification

  1. Field should behave as templated whenever it has a format prop.
  2. Field formatting happens onChange and onBlur to ensure it's strictly followed.
  3. The core functionality for field formatting is a pure function (formatField({ fieldProps }) for example). It should have nothing to do with the form, its context, or whatsoever. Takes props and ensures that the value is in the proper format.
  4. A char # is used as a placeholder for an actual entered value. Therefore, an expected format of format (lol) is ###-###-###, for example. # are then substituted by the actual field value.
  5. Format should be ensure when the field mounts (registers in the form). This way initial values are formatted as well.
  6. (Optional) Maybe we should think of situations when the end developer will not supply the whole format, but its beginning only?

Test scenarios

  1. Credit card number: #### #### #### ####
  2. Phone number: +(###) ### ### ###
  3. Birth date: ##/##/####

Introduce flexible validation messages logic

What

Need to add a flexible validation messages logic, similar to what we have in the existing project.

Why

Comfortable way to handle country-specific validations is a must for a modern form.

Enhancer: Field strength

What

Need to introduce the feature of field's strength.

Why

It's quite applicable to such fields as "passwords", yet can be used for any kind of field.

How

  • Field.props.strengthProp?: RegExp
  • The strength of a field is calculated based on the amount of RegExp groups matched.
  • The strength of a field is stored in Field.props.strength, and can be accessed for rule prop, for example, to prevent the field from being valid unless is matched a certain strength.
  • Field.props.maxStrength is present as well, representing the total amount of strength (RegExp) groups. This is useful to display respective UI elements.
<Field.Input
  strengthProp={(\d{1})([a-Z]+)}
  rule={({ fieldProps }) => field.strength > 3} />

"field.props.value" has obsolete value

What

Field.props.value is obsolete comparing to Field.context.fields[name].value, which gets updated by the Form.

Why

Most likely, since context update is not the reason to trigger re-render, new props are not available down the connectField components tree. Also, Field is not really rendered as controlled, so Form doesn't pass direct props to Field due to ancestral hierarchy.

Bug: Cannot render multiple children from "Field.Group"

What

Cannot render multiple JSX node directly from Field.Group:

A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.

Why

Because Field.Group returns the following in render():

return this.props.children;

I guess in case of children being an array, it would not return properly.

Using "fields" with field groups is not intuitive

What

Fields wrapped in Field.Group are stored on the root level of fields immutable Map for easier iterations and composition. This means that mutable fields have the same key-value structure when converting for the external usage.

Why

Well, this is quite intentional, but not desired for external mutable fields.

How

I think it would be reasonable to create some utility method which would transform iterable fields first, and then convert it to mutable Object. The method would split the field groups into nested Object properties, and be very similar to fieldUtils.serialize(), but maintaining all properties, not only value.

Maybe it's worth to make fieldUtils.serialize() reuse this new utility method as well.

Make "value" a possibly dynamic prop

What

Need to allow to make Field.props.value a dynamic prop (dependant from the form's state).

Why

There's a usecase you would like to have a certain field pre-filled once another field has a certain value:

<Field.Input value={({ fields }) => {
  return fields.anotherField && (fields.anotherField.value === 'foo') && 'Pre-filled value';
}} />

Registration of "Field.Radio" is messed up

What

The way (props) the Field.Radio is registered is messed up.

Why

It's a unique fields which resides as one state entry in Form, but has multiple UI representations (options).

How

Already providing a fix which will propagate proper value and checked to Field.Radio upon the field registration, based on the already set value.

Update contextProps on Field.componentWillReceiveProps (propsPatch)

What

Need to perform a shallow diff in Field. componentWillReceiveProps.

Why

This way we know which props we received "from outside", and update those in contextProps as well.

How

The approach overall is discussable. Maybe there's no need for any diff at all. Maybe the way we treat direct props is not acceptable.

Introduce "onChange" validation

What

Need to introduce the validation on field's change

Why

Giving a real-time feedback is quite crucial

How

  1. Only sync validation should fire onChange.
  2. Async validation should still fire onBlur

Bug: Wrong "invalidFields" order with dynamic fields

What

The array of invalid fields (invalidFields) provided as an argument property of onInvalid callback seems to have messed up order of inputs when there are some dynamic fields appearing during the workflow with the form.

Why

At first, my guess was that the fields reference taken to compose the array of invalid fields is taken at the wrong (past) moment, however, I see that it's taken immediately within the callback itself, so it should be up-to-date.

Steps to reproduce

<Form>
  <Field.Input name="i1" required />

  <Condition when={...}>
    <Field.Input name="i2" required />
  </Condition>

  <Field.Input name="i3" required />
</Form>

Once <Condition> is false, the order of invalidFields is correct ([{ i1 }, { i2 }, { i3 }]).

However, once you provide i1 value, and condition resolves with empty field, then the order is i3, i2.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.