@domain-schema/formik

React Forms generator for Domain Schema

Stats

StarsIssuesVersionUpdatedCreatedSize
@domain-schema/formik
2220.0.343 years ago4 years agoMinified + gzip package size for @domain-schema/formik in KB

Readme

Domain Schema React Forms with Formik

npm version Twitter Follow

Installation

yarn add @domain-schema/formik @domain-schema/core @domain-schema/validation

or

npm install @domain-schema/formik @domain-schema/core @domain-schema/validation

Usage

NOTE: The current version works properly for the web platform only. React Native forms support will be added soon.

Example

Simple form

This is example how you can generate basic form according to schema:

import DomainSchema, { Schema } from '@domain-schema/core';
import { DomainSchemaFormik } from '@domain-schema/formik';

import { Button, Form, RenderField } from '../my-components';

const userFormSchema =
  class User extends Schema {
    __ = { name: 'User' };
    username = {
      type: String,
      input: {
        label: 'Username'
      }
    };
    email = {
      type: String,
      input: {
        type: 'email',
        label: 'Email'
      },
      email: true
    };
    password = {
      type: String,
      input: {
        type: 'password',
        label: 'Password',
      },
      min: 5
    };
    passwordConfirmation = {
      type: String,
      input: {
        type: 'password',
        label: 'Password Confirmation',
      },
      matches: 'password'
    };
  }

// set components globally for all forms
DomainSchemaFormik.setFormComponents({
  input: RenderField,
  form: Form,
  button: Button
});

const userForm =  new DomainSchemaFormik(userFormSchema);

const UserForm = userForm.generateForm({
  label: 'Save'
});

<UserForm onSubmit={values => {
    // handle submit
  }}>

Complex form

The complex form - it is the form that has the nested schema field which represents relations with other schema. So, when one to one relation established - form with flat structure will be generated by default. Also, you could use fieldType property with form value to explicitly set up such behaviour.

const userFormSchema =
  class User extends Schema {
    __ = { name: 'User' };
    username = {
      type: String,
      input: {
        label: 'Username'
      },
      defaultValue: 'User',
      validators: [
        value => {
          return value.length > 3 ? undefined : 'Must Be more than 3 characters';
        }
      ]
    };
    email = {
      type: String,
      input: {
        type: 'email',
        label: 'Email',
        placeholder: 'User email'
      },
      email: true
    };
    profile = {
      type: Profile
    };
    password = {
      type: String,
      input: {
        type: 'password',
        label: 'Password',
      },
      min: 5
    };
    passwordConfirmation = {
      type: String,
      input: {
        type: 'password',
        label: 'Password Confirmation',
      },
      matches: 'password'
    };
  }

class Profile extends Schema {
  __ = { name: 'Profile' };
  firstName = {
    type: String,
    input: {
      label: 'First Name'
    },
    required: {
      value: true,
      msg: 'Required First Name'
    }
  };
  lastName = {
    type: String,
    input: {
      label: 'Last Name'
    },
    optional: true
  };
  user = {
    type: User
  }
}

const userForm =  new DomainSchemaFormik(userFormSchema);

// set components for particular DomainSchemaFormik instance
userForm.setFormComponents({
  input: RenderField,
  form: Form,
  button: Button
});

// change error messages
DomainValidation.setValidationMessages({
  required: ({fieldName}) => {
    return `Field '${fieldName}' is required`
  }
});

const UserForm = userForm.generateForm(
  {
    label: 'Submit',
    className: 'submit-btn',
    disableOnInvalid: false
  },
  { className: 'my-form' }
);

// Defining onSubmit prop is required
<UserForm onSubmit={(values, formikBag) => {
    // handle submit
  }}>

When established one to many relation, the nested schema field which implements
this relation (Group has many users) will be skipped by default. But for nested schema with belonging relation (User belongs to Group) fieldType select will be applied.

class Group extends Schema {
  __ = { name: 'Group' };
  id = {
    type: DomainSchema.Int
  };
  name = {
    type: String,
    input: {
      label: 'Group Name'
    }
  };
  description = {
    type: String,
    input:{ 
      label: 'Group Description'
    }
  };
  users = {
    type: [User]
  };
}
class User extends Schema {
  __ = { name: 'User' };
  id= {
    type: DomainSchema.Int
  };
  name = {
    type: String,
    input: {
      type: 'text',
      label: 'Username'
    },
  };
  email = {
    type: String,
    input: {
      type: 'email',
      label: 'Email',
      placeholder: 'User email'
    }
  };
  group = {
    type: Group
  };
  password = {
    type: String,
    input: {
      type: 'password',
      label: 'Password'
    },
    min: 5
  };
}

const groupForm =  new DomainSchemaFormik(Group);

// set components for particular DomainSchemaFormik instance
groupForm.setFormComponents({
  input: RenderField,
  select: RenderSelectQuery,
  form: Form,
  button: Button
});

const GroupForm = groupForm.generateForm(
  {
    label: 'Submit',
    className: 'submit-btn',
    disableOnInvalid: false
  }
);

// Defining onSubmit prop is required
<GroupForm onSubmit={(values, formikBag) => {
    // handle submit
  }}>

To prevent generating field use ignore flag.

Using different field types

To pass attributes/properties to the field component use input property in a schema. All fields can take 3 special attributes: onChange, onBlur and type. All other defined attributes will be directly passed to the field component. Attribute name will be define automatically and equals field name from the schema.

  • input - is default field type and it can be omitted

      email = {
        ...
        fieldType: 'input',
        input: {
          type: 'email',
          placeholder: 'User Email'
        }
        ...
      }
      post = {
        ...
        // fieldType: 'input'
        input: {
          placeholder: 'Your post',
          type: 'textarea',
          label: 'Post'
        }
        ...
      }
    
  • checkbox

      active = {
        ...
        fieldType: 'checkbox',
        input: {
          label: 'Active'
        },
        defaultValue: true
      }
    
  • select

      role = {
        ...
        fieldType: 'select',
        input: {
          label: 'User role',
          values: ['user', 'admin']
        }
        ...
      }
    
  • radio

      friend = {
        ...
        fieldType: 'radio',
        input: {
          label: 'Very best friend',
          values: ['Gerald', 'Ashley']
        },
        ...
      }
    

To prevent the generation of a specific field we can use ignore property

  password = {
      type: String,
      input: {
        type: 'password',
        label: 'Password',
      },
      ignore: true
    };

Field components will get next props:

{
      // all props which were specified in the input property in the schema
      ...,
      onChange, // if the callback was not specified in the input property, the Formik's handleChange will be used
      onBlur, // the same as OnChange
      type, // if the type was not specified in the input property, the fieldType will be used, if the fieldType is missing too, 'text' will be used
      name, //  will be define automatically and equals field name from the schema
      value, // if the defaultValue was not specified, the empty will be used as value
      meta // object with touched and error properties from Formik
}

Buttons

To create a submit button in the form we need to pass object with attributes to the generateForm method. All attributes, that we define in the object will be passed to the button:

  ...
  userForm.generateForm({
    label: 'Submit',
    className: 'submit-btn',
    color: 'primary'
  })

By default, submit button is disabled when form is invalid. To deactivate that behaviour we can define disableOnInvalid as false

  userForm.generateForm({
    label: 'Save',
    disableOnInvalid: false
  })

In some cases, the submit button is not enough for us. We should pass object that has submit and reset propperties. Reset button resets the form-data to its initial values.

  userForm.generateForm({
    submit: {
      label: 'Submit',
      className: 'submit-btn'
    },
    reset: {
      label: 'Reset',
      className: 'reset-btn'
    }
  })

For position styling buttons have the align property which can take on values left or right. By default, value is center.

  userForm.generateForm({
    ...
    align: 'left'
  })

Buttons are wrapped in a div with style display: flex, so that any properties for flex items can be applied to them. For example, the order of the buttons can be changed using order.

  ...
  .submit-btn {
    order: 1
  }
  .reset-btn {
    order: 0
  }
  ...
  userForm.generateForm({
    submit: {
      ...
      className: 'submit-btn'
    },
    reset: {
      ...
      className: 'reset-btn'
    }
  })

Set default field types

Default field types can be set globally.

...
DomainSchemaFormik.setDefaultFormFieldTypes({
    oneToOneFieldType: 'form',
    plainFieldType: 'input',
    oneToManyFieldType: 'select'
});
...

Set form components

Form components can be set in two ways. Globally, for all forms:

...
DomainSchemaFormik.setFormComponents({
  input: RenderField,
  select: RenderSelect,
  checkbox: RenderCheckBox,
  form: Form,
  button: Button
});
...

And locally, for particular DomainSchemaFormik instance:

...
const userForm =  new DomainSchemaFormik(userFormSchema);

userForm.setFormComponents({
  input: RenderField,
  select: RenderSelect,
  checkbox: RenderCheckBox,
  form: Form,
  button: Button
});
...

Form submitting

We should define onSubmit callback which is received values from form fields as values and helper methods as formikBag. More about these methods in official docs FormikBag.

<UserForm onSubmit={(values, formikBag) => {
    // handle submit
  }}>

Generate form fields without form

If we need maximum flexibility, we can generate only fields without the form itself. For that we should use generateFields method instead of generateForm. But note, that you should use Formik manually when you generating fields without form.

const fieldSet = userForm.generateFields();
...
return <form>{fieldSet}</form>;

Custom field generation

We can use custom field, defining fieldType as custom and specified field component in component prop. All necessary props can be provided via input.

  myField = {
    fieldType: 'custom',
    component: MyFieldComponent,
    input: {
      // all props that our component may need
    }
  }

Validation

Please check @domain-scheam/validation documentation.

License

Copyright © 2017-2018 SysGears INC. This source code is licensed under the MIT license.

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.