Skip to content

Latest commit

 

History

History
578 lines (453 loc) · 13.4 KB

README.md

File metadata and controls

578 lines (453 loc) · 13.4 KB

Formup

NPM Languages Stars Forks Contributors

Integrated form validation using Formik + Yup.

Formup integrates Formik with Yup, reducing the code overhead needed to create your forms to zero!

It provides the best of both worlds for all of your forms so you can create, initialize and validate any form in only a few lines of code 📝💯.

Of course, you'll still have all validation options and functionality from Yup and all helpers from Formik. Formup is essentially a bridge between these two libraries so that you can work easily without worrying about writing any middleware.

Online Example

Online example here.

Installation

You can use yarn

yarn add @formup/core

Or npm, it's totally up to you!

npm install --save @formup/core

Usage

import * as React from 'react';
import * as yup from 'yup';

import { useFormup } from '@formup/core';

const schema = yup.object().shape({
  name: yup
    .string()
    .required()
    .label('Name'),
  email: yup.string()
    .required()
    .email()
    .label('Email'),
  age: yup
    .number()
    .integer()
    .positive()
    .required()
    .label('Age'),
});

const MyComponent = () => {
  const handleValidationError = (errors) => {
    console.warn('Form validation error!', errors);
  };

  const handleSubmitForm = (values) => {
    const {
      name,
      phone,
      email,
      password,
      confirmPassword,
    } = values;

    console.warn('Form is valid! Submitting information...', {
      name,
      phone,
      email,
      password,
      confirmPassword,
    });

    // Submit your form to your backend or any API here! =).
    return true;
  };

  const {
    formikForm,
    submitForm,
    FormInput,
    Form,
  } = useFormup(schema, {
    onError: handleValidationError,
    onSubmit: handleSubmitForm,

    /**
     * When this prop is set to true, formup will apply transform functions defined
     * in your schema before submitting the values.
     */
    transformOnSubmit: true,
  });

  return (
    <Form formikForm={formikForm}>
      {/*
        FormInput will take care of all validation and property mapping!

        Properties such as "label" will be automatically inherited from your
        schema, but you can override them by passing the prop to FormInput.

        You simply need to provide the "name" prop.
      */}

      <FormInput name="name" />
      <FormInput name="email" />
      <FormInput name="age" label="Custom Age Label" />

      <button type="button" className="form-button" onClick={submitForm}>
        Submit!
      </button>
    </Form>
  );
};

export default MyComponent;

Usage - Rendering Custom Inputs

You can render any custom component while still keeping all validation from Formup.

To do this, you just need to pass the component prop to FormInput. 🙋

import * as React from 'react';
import * as yup from 'yup';

import { useFormup } from '@formup/core';

const schema = yup.object().shape({
  name: yup
    .string()
    .required()
    .label('Name'),
});

// We can easily render custom components using Formup!
const CustomInput = ({
  title, // Custom property
  ...props
}) => (
  <div>
    <p>{title}</p>
    <input {...props} />
  </div>
);

const MyComponent = () => {
  const handleSubmitForm = (values) => {
    console.warn('Form is valid! Submitting information...', values);

    // Submit your form to your backend or any API here! =).
    return true;
  };

  const {
    formikForm,
    submitForm,
    FormInput,
    Form,
  } = useFormup(schema, {
    onSubmit: handleSubmitForm,
  });

  return (
    <Form formikForm={formikForm}>
      {/*
        Here we'll render FormInput, but with a custom component!
      */}

      <FormInput
        name="name"
        component={CustomInput}
        title="Sign your name here, please!"
      />

      <button type="button" className="form-button" onClick={submitForm}>
        Submit!
      </button>
    </Form>
  );
};

export default MyComponent;

Usage - Single-choice input groups

Formup can take care of input groups, such as checkboxes too!

You can also define an initialValue.

Here's an example:

const {
  formikForm,
  Form,
  FormInputGroup,
  FormInputGroupItem,
} = useFormup(...);

<Form formikForm={formikForm}>
  <FormInputGroup name="gender" initialValue="Male">
    <p>What's your gender?</p>

    <FormInputGroupItem component={MyCustomRadioButton} value="Male" />
    <FormInputGroupItem component={MyCustomRadioButton} value="Female" />
    <FormInputGroupItem component={MyCustomRadioButton} value="Non-binary" />
  </FormInputGroup>
</Form>

Usage - Multiple-choice input groups

What did you expect? Of course we support multiple choice input groups too =).

Just pass multi to FormInputGroup.

You can also define an initialValue, as an array with multiple options.

Here's an example:

const {
  formikForm,
  Form,
  FormInputGroup,
  FormInputGroupItem,
} = useFormup(...);

<Form formikForm={formikForm}>
  <FormInputGroup name="favoriteFood" multi initialValue={['Oreo', 'Pie']}>
    <p>What's your gender?</p>

    <FormInputGroupItem value="Ice Cream Sandwich" component={Checkbox} />
    <FormInputGroupItem value="KitKat" component={Checkbox} />
    <FormInputGroupItem value="Lollipop" component={Checkbox} />
    <FormInputGroupItem value="Nougat" component={Checkbox} />
    <FormInputGroupItem value="Oreo" component={Checkbox} />
    <FormInputGroupItem value="Pie" component={Checkbox} />
  </FormInputGroup>
</Form>

Usage - Rendering array of objects

You can use formup to easily render any array type, by using FormArrayField.

The FormArrayField component will provide both the items from the array field and an arrayHelpers object which contains methods to add or remove new items to the list.

Here's an example:

const {
 formikForm,
 Form,
 FormArrayField,
} = useFormup(...);

<Form formikForm={formikForm}>
 <FormArrayField name="colors">
   {(items, arrayHelpers) => (
     <>
       {items.map((item, index) => (
         <div>
           <FormInput
             component={TextFieldWithErrorMessage}
             injectFormupData
             name={item.path}
           />

           <button
             onClick={() => arrayHelpers.remove(index)}
             type="button"
           >
             Remove item
           </button>
         </div>
       ))}

       <button
         onClick={() => arrayHelpers.push()}
         type="button"
       >
         Add item
       </button>
     </>
   )}
 </FormArrayField>
</Form>

But don't worry, if your array contains a complex object inside of it, FormArrayField can help as well.

You can use the getPath function in each item to get the full path of the nested object of that item of the list.

And you can also push a new item with initial values, pretty cool, isn't it?

Here's an example:

const {
 formikForm,
 Form,
 FormArrayField,
} = useFormup(...);

<Form formikForm={formikForm}>
 <FormArrayField name="familyMembers">
   {(items, arrayHelpers) => (
     <>
       {items.map((item, index) => (
         <div>
           <FormInput
             component={TextFieldWithErrorMessage}
             name={item.getPath('name')}
             injectFormupData
           />

           <FormInput
             component={TextFieldWithErrorMessage}
             name={item.getPath('age')}
             injectFormupData
           />

           <button
             onClick={() => arrayHelpers.remove(index)}
             type="button"
           >
             Remove item
           </button>
         </div>
       ))}

       <button
         onClick={() => arrayHelpers.push({
           name: 'John Foo clone',
           age: 10,
         })}
         type="button"
       >
         Add item
       </button>
     </>
   )}
 </FormArrayField>
</Form>

Usage - Extended Formup Data in Rendered Inputs

You can gather extended formup data in order to aid your custom inputs, such as the validation error message, for example.

To do this, you just need to pass the injectFormupData prop to FormInput. 🙋

This will mean that FormInput will inject a prop named formupData into your component, that contains extra information, such as the error message.

⚠️ This is disabled by default to maintain compatibility. If enabled, you need to make sure that formupData won't be injected into the final <input /> component, otherwise React will throw an error saying that formupData is an invalid property for <input />.

Here's a quick example:

  • In FormInput, add the injectFormupData prop.
  <FormInput
    component={CustomInputWithErrorMessage}
    injectFormupData={true}
    name="email"
  />
  • In your component, handle formupData prop:
const CustomInputWithErrorMessage = ({
  formupData,
  ...props
}) => (
  <>
    {
      formupData && formupData.errorMessage && (
        <label>
          {`Error: ${formupData && formupData.errorMessage}`}
        </label>
      )
    }

    <input
      {...props}
    />
  </>
);

Usage - Custom Validation

When submitting your form with formup, note that your form won't be validated if it isn't valid.

You can choose to validate the whole form (which formup does by default), or:

  • Validate only one field
  • Validate an array of fields
  • Validate only one object (nested fields)
  • Validate an array of objects (nested fields)
  • Validate fields + objects

To do this, you simply need to pass validationOptions.path to formup submitForm options:

const {
  submitForm,
} = useFormup(schema, {
  onSubmit: handleSubmitForm,
});

// This will validate only "name" field
submitForm({
  validationOptions: {
    paths: 'name',
  },
});

// This will validate "name" and "phone" fields
submitForm({
  validationOptions: {
    paths: [
      'name',
      'phone',
    ],
  },
});

// This will validate all fields within "personalInformation" object
submitForm({
  validationOptions: {
    paths: 'personalInformation',
  },
});

// This will validate the field "document" within "personalInformation" object,
// the field "job" within "professionalInformation",
// and also the "name" field
submitForm({
  validationOptions: {
    paths: [
      'personalInformation.document',
      'professionalInformation.job',
      'name',
    ],
  },
});

Usage - Custom form initial values

By default, formup will already create the form initial values according to your yup schema. This means that any default(...) will be taken into consideration, and it will always initialize the form as an empty valid object of your schema.

For example, the schema:

const schema = yup.object().shape({
  userId: yup
    .number()
    .default(100),
  personalInformation: yup.object().shape({
    firstName: yup
      .string()
      .required(),
    email: yup
      .string()
      .default("[email protected]")
      .required(),
  }),
});

Will automatically translate to:

{
  userId: 100,
  personalInformation: {
    firstName: "",
    email: "[email protected]",
  },
}

However, you can customize any value generated within your initial values by passing initialValues to useFormup options. Formup will automatically merge the two objects into one, taking your overrides into consideration.

Here's an example, using the schema declared above:

useFormup(schema, {
  initialValues: {
    userId: 999,
    personalInformation: {
      firstName: "Foo",
    },
  },
});

This will produce:

{
  userId: 999,
  personalInformation: {
    firstName: "Foo",
    email: "[email protected]",
  },
}

Contributing

Pull requests are welcome!

If you have any feedback, issue or suggestion, feel free to open a new issue so we can talk about it 💬.

Local testing

To test this locally we can use the example application!

  • You need to update the dependencies at example's package.json to run locally:
"dependencies": {
  "@formup/core": "link:../packages/core/dist",
  "react": "link:../packages/core/node_modules/react",
  "react-dom": "link:../packages/core/node_modules/react-dom",
  ...other dependencies
},
  • Then at example, run:
yarn install

yarn start
  • Whenever you change any file at /packages/core, you need to rebuild for changes to be applied:
yarn build

License

MIT © formup

Special Thanks

Thanks to jaredpalmer and all maintainers for developing formik.

Thanks to jquense and all maintainers for developing yup.

Made possible by