@mithya-team/react-form-plugin
TypeScript icon, indicating that this package has built-in type declarations

0.0.5 • Public • Published

React Dynamic Form Library

A flexible and dynamic form library for React, featuring a generic form component capable of rendering various input types, including custom dynamic inputs. Built with accessibility and extensibility in mind.

Features

  • Dynamic Form Fields: Render various input types based on provided configurations.
  • Custom Input Types: Easily extend with custom input types.
  • Form State Management: Integrated with react-hook-form for robust form state management and validation.
  • Accessibility: Inputs come with basic ARIA attributes and can be further customized for accessibility.
  • Controlled Components: Manage and manipulate form inputs dynamically from parent components.

Installation

Install the package using npm or yarn:

npm install @mithya-team/react-form-plugin

or

yarn add @mithya-team/react-form-plugin

Usage

Integrating and utilizing the ReactForm component in your React application is straightforward. Follow these steps to render and manage forms dynamically.

Basic Form Setup

Here's a basic example to get you started:

import React from "react";
import { ReactForm } from "@mithya-team/react-form-plugin";

const MyForm = () => {
  const fieldsInput = [
    { type: "text", name: "name", label: "Name" },
    // ...add other field configurations
  ];

  const onSubmit = (data) => {
    // Handle form submission data
    console.log(data);
  };

  return <ReactForm fieldsInput={fieldsInput} onSubmit={onSubmit} />;
};

export default MyForm;

Validation

Example showing validation with zod

import React, { useState } from "react";
import { FieldInput, ReactForm } from "@mithya-team/react-form-plugin";
// import validation library
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  name: z.string().min(1, "Name is required"),
  emailId: z.string().email("Invalid email format").min(1, "Email is required"),
  gender: z.enum(["male", "female", "trans"], {
    required_error: "Gender is required",
  }),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  { type: "text", name: "name", label: "Name", defaultValue: "Ram" },
  { type: "email", name: "emailId", label: "Email ID" },
  {
    type: "radio",
    name: "gender",
    label: "Gender",
    options: [
      { value: "male", label: "Male" },
      { value: "female", label: "Female" },
      { value: "trans", label: "Transgender" },
    ],
  },
];

const UserDetailsForm: React.FC = () => {
  const onSubmit = (data: any) => console.log(data);

  return (
    <div>
      <ReactForm
        fieldsInput={fieldsInput}
        onSubmit={onSubmit}
        resolver={zodResolver(schema)}
      />
      <button onClick={logFormValues}>Log Form Values</button>
    </div>
  );
};

export default UserDetailsForm;

Conditional Update

Use conditions to update the field props

import React from "react";
import { FieldInput, ReactForm } from "@mithya-team/react-form-plugin";

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "radio",
    name: "gender",
    label: "Gender",
    options: [
      { value: "male", label: "Male" },
      { value: "female", label: "Female" },
      { value: "trans", label: "Transgender" },
    ],
  },
  {
    type: "select",
    name: "favouritePokemon",
    label: "Favourite Pokemon",
    options: [
      { value: "pik", label: "Pikachu" },
      { value: "char", label: "Charmander" },
      { value: "saiduck", label: "Saiduck" },
    ],
    inputProps: { disabled: true },
    conditions: [
      {
        when: (formValues) => {
          return formValues["gender"] === "male";
        },
        then: () => {
          return { inputProps: { disabled: false } };
        },
      },
    ],
  },
];

const UserDetailsForm: React.FC = () => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const onSubmit = (data: any) => console.log(data);

  return (
    <div>
      <ReactForm fields={fieldsInput} onSubmit={onSubmit} />
    </div>
  );
};

export default UserDetailsForm;

Extending Custom Input Types

Let's take a cue from OtpInput

Step 1: Create a custom Input Type

import React, { useEffect, useRef } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { ErrorField } from "@mithya-team/react-form-plugin";
import { OtpInputProps } from "../../@types";
import "../index.css";

export interface MyCustomFieldProps {
  name: string; // name is required
  label?: string;
  classes?: {
    root?: string;
    // other classes for styling goes here
  };
  inputProps?: IInputProps;
  defaultValue?: string;
}

const MyCustomField: React.FC<MyCustomFieldProps> = ({
  name,
  length,
  classes,
  label,
  inputProps,
}) => {
  const {
    control,
    formState: { errors },
  } = useFormContext();

  const inputRef = useRef<(HTMLInputElement | null)>(null);


  const id = `form-${name}`;

  return (
    <>
      <Controller
        name={name}
        control={control}
        render={ ({ field: { onChange, onBlur, value = "" } }) => (

          // Render React Component here

        )}
      />
      <ErrorField
        id={`${id}-error`}
        className={classes?.error}
        error={errors?.[name]}
      />
    </>
  );
};

export default MyCustomField;

Step 2: Add custom input types by extending the input component map:

import { addtype } from "@mithya-team/react-form-plugin";
import type { FormFields } from "@mithya-team/react-form-plugin";
import MyCustomField from "./MyCustomInput";

// Extend the type
type ExtendedFormFields =
  | FormFields
  | Field<"my-custom-field", MyCustomFieldProps>;

addtype("my-custom-field", MyCustomField);

Step 3: Usage in ReactForm

// Define the form fields
const fieldsInput: ExtendedFormFields[] = [
  {
    type: "radio",
    name: "gender",
    label: "Gender",
    options: [
      { value: "male", label: "Male" },
      { value: "female", label: "Female" },
      { value: "trans", label: "Transgender" },
    ],
  },
  // use here, it should support typescript intellisense
  {
    type: "my-custom-field",
    name: "CustomField",
    // other custom field props
  }

API Reference

Below is the API reference for the ReactForm component and its associated input types.

ReactForm

A flexible and dynamic form component that renders based on provided field configurations.

Props

  • fieldsInput: Array of field configuration objects
  • onSubmit: Function to handle form submission.
  • onGetValues: Function to expose the getValues method from react-hook-form
  • classes: CSS Classes for styling
  • resolver: Use any validation library supported by react-hook-form

Field Configuration

Defines how each field in the form should be rendered and behave.

Properties

  • type: Defines the type of input (e.g., 'text', 'checkbox', 'radio').
  • name: A unique identifier for the form field. label (optional): Display label for the form field.

Additional properties for validation, aria attributes, and other custom behaviors.

TextField config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  name: z.string().min(1, "Name is required"),
  address: z.string().min(6, "Address is required"),
  emailId: z.string().email("Invalid email format").min(1, "Email is required"),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "text",
    name: "name",
    label: "Name",
    defaultValue: "Ram",
    inputProps: {
      disabled: true,
    },
  },
  {
    type: "text",
    name: "address",
    label: "Address",
    defaultValue: "8, Dasrath Palace, Ayodhya",
  },
  { type: "email", name: "emailId", label: "Email ID" },
];

Props

type BasicInputProps = {
  type: `${BasicInputType}`;
  name: string;
  label?: string;
  classes?: {
    container?: string;
    inputContainer?: string;
    label?: string;
    input?: string;
    error?: string;
  };
  inputProps?: IInputProps;
  defaultValue?: string;
};

interface IInputProps
  extends Omit<
    InputHTMLAttributes<HTMLInputElement | HTMLSelectElement>,
    "type"
  > {
  startAdornment?: ReactElement;
  endAdornment?: ReactElement;
}

Styling

  const classes = {
    container?: string; // rf--basic-input-root
    inputContainer?: string; // rf--input-container
    label?: string; // rf--label
    input?: string; // rf--input
    error?: string; // rf--error
    endAdornment?: string; // rf--start-adornment
    startAdornment?: string; // rf--end-adornment
  };

Checkbox config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  favouriteDish: z
    .array(z.string())
    .nonempty("Select at least one favourite dish")
    .optional(), // mark the field as optional if conditional rendering is applied
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "checkbox",
    name: "favouriteDish",
    label: "Favourite Dish",
    options: [
      { value: "rice", label: "Rice" },
      { value: "pizza", label: "Pizza" },
      { value: "burger", label: "Burger" },
    ],
    defaultValue: ["rice"],
  },
];

Props

type Option = {
  value: string;
  label: string;
  disabled?: boolean;
};

interface CheckboxInputProps {
  type: "checkbox";
  name: string;
  options: Option[];
  label?: string;
  classes?: {
    root?: string;
    container?: string;
    checkbox?: string;
    label?: string;
    error?: string;
  };
  inputProps?: IInputProps;
  defaultValue?: Option["value"][];
}

Styling

const classes = {
    root?: string; // rf--checkbox-root
    container?: string; // rf--checkbox-container
    checkbox?: string; // rf--checkbox-input
    label?: string; // rf--label
    error?: string; // rf--error
  };

select config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  favouritePokemon: z
    .enum(["pik", "char", "saiduck"], {
      required_error: "Pokemons is required",
    })
    .optional(), // mark the field as optional if conditional rendering is applied
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "select",
    name: "favouritePokemon",
    label: "Favourite Pokemon",
    options: [
      { value: "pik", label: "Pikachu" },
      { value: "char", label: "Charmander" },
      { value: "saiduck", label: "Saiduck" },
    ],
  },
];

Props

interface SelectInputProps {
  type: "select";
  name: string;
  options: Option[];
  label?: string;
  classes?: {
    root?: string;
    container?: string;
    select?: string;
    label?: string;
    option?: string;
    error?: string;
  };
  inputProps?: IInputProps;
  defaultValue?: Option["value"];
}

Styling

const classes = {
    root?: string; // rf--select-root
    select?: string; // rf--select-input
    label?: string; // rf--label
    option?: string; // rf--label rf--select-label
    error?: string; // rf--error
  };

Radio config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  gender: z.enum(["male", "female", "trans"], {
    required_error: "Gender is required",
  }),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "radio",
    name: "gender",
    label: "Gender",
    options: [
      { value: "male", label: "Male" },
      { value: "female", label: "Female" },
      { value: "trans", label: "Transgender" },
    ],
  },
];

Props

interface RadioInputProps {
  type: "radio";
  name: string;
  options: Option[];
  label?: string;
  classes?: {
    root?: string;
    container?: string;
    radio?: string;
    label?: string;
    error?: string;
    rootLabel?: string;
  };
  inputProps?: IInputProps;
  defaultValue?: Option["value"];
}

Styling

const classes = {
    root?: string; // rf--radio-root
    container?: string; // rf--radio-container
    radio?: string; // rf--radio-input
    label?: string; // rf-label rf--radio-label
    error?: string; // rf--error
    rootLabel?: string; // rf--label
  };

switch config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  preferredTheme: z.boolean({ invalid_type_error: "value should be boolean" }),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "switch",
    name: "preferredTheme",
    label: "Dark Theme",
    defaultValue: true,
  },
];

Props

interface SwitchInputProps {
  type: "switch";
  name: string;
  label?: string;
  classes?: {
    switchContainer?: string;
    label?: string;
    switch?: string;
    button?: string;
    input?: string;
    activeSwitch?: string;
    activeButton?: string;
  };
  inputProps?: IInputProps;
  defaultValue?: boolean;
}

Styling

const classes = {
    switchContainer?: string; // rf--switch-root
    label?: string; // rf--switch-label rf--label
    switch?: string; // rf--label rf--switch
    button?: string; // rf--switch-button
    input?: string; // rf--switch-input
    activeSwitch?: string;
    activeButton?: string;
  };

File config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  file: z.any().refine((files) => files?.length == 1, "Image is required."),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "file",
    name: "file",
    label: "file",
  },
];

Props

Same as BasicInput

Styling

Same as BasicInput

date and time config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  time: z.string(),
  date: z.string(),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "time",
    name: "time",
    label: "Time",
    defaultValue: true,
  },

  {
    type: "date",
    name: "date",
    label: "Date",
    defaultValue: true,
  },
];

Props

Same as BasicInput

Styling

Same as BasicInput

Otp config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  phoneOtp: z.string(),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "otp",
    name: "phoneOtp",
    length: 6,
  },
];

Props

interface OtpInputProps {
  type: "otp";
  name: string;
  length: number;
  label?: string;
  classes?: {
    root?: string;
    input?: string;
    error?: string;
    label?: string;
    inputContainer?: string;
  };
  inputProps?: IInputProps;
  defaultValue?: string;
}

Styling

const classes = {
    root?: string; // rf--otp-root
    input?: string; // rf--input rf--otp-input
    error?: string; // rf--error
    label?: string; // rf--label
    inputContainer?: string; // rf--otp-input-container
  };

phone number config example

import { FieldInput } from "../../lib";
import { z } from "zod";

// Define the validation schema using Zod
const schema = z.object({
  countryPhoneNumber: z.string(),
});

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "country-phone-number",
    name: "countryPhoneNumber",
  },
];

Props

interface CountryPhoneNumberInputProps {
  type: "country-phone-number";
  name: string;
  classes?: {
    root?: string;
    select?: string;
    input?: string;
    inputWrapper?: string;
    selectWrapper?: string;
    error?: string;
    label?: string;
  };
  label?: string;
  inputProps?: IInputProps;
  selectProps?: IInputProps;
  defaultValue?: string;
}

Styling

const classes = {
    root?: string; // rf--phone-number-root
    select?: string; // rf--phone-number-country-select
    input?: string; // rf--input rf--phone-number-input
    inputWrapper?: string; // rf--phone-number-input-wrapper
    selectWrapper?: string; // rf--phone-number-country-select-wrapper
    error?: string; // rf--error
    label?: string; // rf--label
  };

plain text config example

// Define the form fields
const fieldsInput: FieldInput[] = [
  {
    type: "plainText",
    name: "plainHelperText",
    text: "This is helper text.",
  },
];

Props

interface PlainTextProps {
  type: "plainText";
  name: string;
  text?: string | JSX.Element;
  className?: string;
}

Styling

className?: string; // rf--plain-text

Issues and Pull Requests

Contributions are always welcome! If you encounter any issues or would like to contribute, please file an issue or submit a PR:

Issues: GitHub Issues Pull Requests: GitHub Pull Requests

Readme

Keywords

none

Package Sidebar

Install

npm i @mithya-team/react-form-plugin

Weekly Downloads

1

Version

0.0.5

License

MIT

Unpacked Size

149 kB

Total Files

30

Last publish

Collaborators

  • mithyalabs
  • titan_mithya