@nolwenture/formik-jsonschema-form
TypeScript icon, indicating that this package has built-in type declarations

0.8.0 • Public • Published

formik-jsonschema-form

Installation

With npm:

npm install @nolwenture/formik-jsonschema-form

With yarn:

yarn add @nolwenture/formik-jsonschema-form

Testing

Run unit tests:

npm run test

To run storybook tests make sure you have Playwright installed:

npx playwright install

Run storybook tests:

npm run test-storybook

Usage

Basic usage

Pass your schema to the Form component, which should be passed as a child to the Formik component, for example:

import { Form } from "@nolwenture/formik-jsonschema-form";
import { Formik } from "formik";

const schema = {
  title: "My form",
  type: "object",
  properties: {
    firstName: { type: "string", title: "First name" },
    lastName: { type: "string", title: "Last name" },
  },
};

const uiSchema = {
  "ui:description": "Form description",
  firstName: { "ui:description": "Field description" },
};

export function MyFormComponent() {
  return (
    <Formik
      initialValues={{ firstName: "", lastName: "" }}
      onSubmit={handleSubmit}
    >
      <Form schema={schema} uiSchema={uiSchema}></Form>
    </Formik>
  );
}

Pass your uiSchema to the Formcomponent. which should be passed as a child to the Formikcomponent, just like schema. uiSchema will tell the form more about its own layout and styles.

Basic available elements to add to the form or a field of the form via uiSchema are:

  • "ui:title"
  • "ui:description"
  • "ui:help"
  • "ui:options"
  • "ui:info"

Most of those elements take a string for value, but ui:info takes an object with the possible keys of

  • label for the clickable anchor (if not provided, default value is "i")
  • content for the content of the popup container, and
  • position taking a string of either "top", "bottom", "left" or "right" (if not provided, default value is "top")

All elements of ui:info are customisable. The styledComponent is called InfoFieldButton and it consists of the following elements:

  • InfoFieldContainerElement
  • InfoFieldBoxElement
  • InfoFieldBoxInnerElement
  • InfoFieldBoxArrowElement
  • InfoFieldLabel
import { Form } from "@nolwenture/formik-jsonschema-form";
import { Formik } from "formik";

const schema = {
  title: "My form",
  type: "object",
  properties: {
    firstName: { type: "string", title: "First name" },
    lastName: { type: "string", title: "Last name" },
  },
};

const uiSchema = {
  "ui:description": "Form description",
  firstName: { "ui:description": "Field description" },
  lastName: {
    "ui:info": {
      label: "?",
      content: "Add your family name",
    },
  },
};

export function MyFormComponent() {
  return (
    <Formik
      initialValues={{ firstName: "", lastName: "" }}
      onSubmit={handleSubmit}
    >
      <Form schema={schema} uiSchema={uiSchema}></Form>
    </Formik>
  );
}

Using custom components

Form components (such as wrappers) can be overridden with custom ones

Here is an example of overriding FieldWrapper (wrapper used on all fields):

function CustomFieldWrapper({ children }) {
  return (
    <div
      style={{
        backgroundColor: "lightgrey",
        boxShadow: "1px 1px 5px gray",
        margin: "10px",
        padding: "10px",
      }}
    >
      {children}
    </div>
  );
}

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form
      schema={schema}
      uiSchema={uiSchema}
      components={{
        FieldWrapper: CustomFieldWrapper,
      }}
    ></Form>
  </Formik>
);

And another example how to override InfoFieldButton:

const CustomInfoFieldButton = styled(InfoFieldButton)`
  outline: 2px red solid;
  margin: 0.5em;
  ${InfoFieldButton.styles.InfoFieldBoxInnerElementStyle} {
    min-width: 200px;
    background: orange;
    color: blue;
  }
  ${InfoFieldButton.styles.InfoFieldLabelStyle} {
    color: green;
  }
`;

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form
      schema={schema}
      uiSchema={uiSchema}
      components={{
        InfoFieldButton: CustomInfoFieldButton,
      }}
    ></Form>
  </Formik>
);

Custom fields can be used in addition to all the default fields:

import { Form, FormRegistry } from "@nolwenture/formik-jsonschema-form";
import { useContext } from "react";

const schema = {
  title: "My form",
  type: "object",
  properties: {
    firstField: { title: "First field" },
  },
};

function MyCustomFieldComponent(props) {
  const {
    templates: { FieldTemplate },
  } = useContext(FormRegistry);

  return (
    <FieldTemplate {...props}>
      <p>My awesome field!</p>
    </FieldTemplate>
  );
}

const fields = { myField: MyCustomFieldComponent };

// Specify the fields that should use your custom field component in uiSchema
const uiSchema = {
  firstField = { "ui:field": "myField" },
};

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form schema={schema} uiSchema={uiSchema} fields={fields}></Form>
  </Formik>
);

The style for buttons to add and remove (and move) array or object items can be overridden within the uiSchema by giving inline style.

The buttons that can be customized that way are:

  • "ui:addButtonOptions"
  • "ui:removeButtonOptions"
  • "ui:upButtonOptions"
  • "ui:downButtonOptions"
  • "ui:customButtonOptions"

content takes a ReactNode as input.

const schema = {
  title: "Form with customized buttons",
  type: "object",
  properties: {
    someArray: {
      type: "array",
      title: "Some array",
      description: "Array with customized buttons",
      additionalItems: {
        type: "string",
      },
    },
  }
}

const uiSchema = {
  "ui:description": "Form description",
  // make a green ADD button for someArray that reads "Hello!"
    someArray: {
    "ui:addButtonOptions": {
      content: <b>Hello!</b>,
      props: { style: { background: "green" } },
    },
};

...

return (
  <Formik initialValues={initialValues} onSubmit={handleOnSubmit}>
    <Form
      schema={schema}
      uiSchema={uiSchema}
    ></Form>
  </Formik>
);

Required input

Required input can be displayed with an asterisk next to the title. The respective fieldname needs to be given inside the required-array of the object holding the field in question in the schema. In the example, required fields are 'lastName', 'favouriteFood' inside the arrayOfObject, and 'tasks' in nestedObject.

The styling of the asterisk can be customised by applying style to styledComponents.RequiredIndicator.

import { Form } from "@nolwenture/formik-jsonschema-form";
import { Formik } from "formik";

const CustomizedRequiredIndicator = styled(styledComponents.RequiredIndicator)`
  color: aqua;
`;

const schema = {
  title: "My Form",
  required: ["lastName"],
  properties: {
    firstName: { type: "string", title: "First name" },
    lastName: { type: "string", title: "Last name" },
    arrayOfObject: {
      type: "array",
      title: "Array of objects",
      items: {
        type: "object",
        title: "Favourites",
        required: ["favouriteFood"],
        properties: {
          favouriteFood: { type: "string", title: "Favourite food" },
          favouriteSong: { type: "string", title: "Favourite song" },
        },
      },
    },
    nestedObject: {
      type: "object",
      title: "Nested Object",
      required: ["tasks"],
      properties: {
        hobbies: { type: "string", title: "Hobbies" },
        tasks: { type: "string", title: "Tasks" },
      },
    },
  },
};

const uiSchema = {
  "ui:description": "Form description",
  firstName: { "ui:description": "Field description" },
};

export function MyFormComponent() {
  return (
    <Formik
      initialValues={{ firstName: "", lastName: "" }}
      onSubmit={handleSubmit}
    >
      <Form
        schema={schema}
        uiSchema={uiSchema}
        components={{ RequiredIndicator: CustomizedRequiredIndicator }}
      ></Form>
    </Formik>
  );
}

Enum fields

Enum fields are fields with multiple options to either choose oneOf or anyOf from. There is an alternative field implementation for "anyOf" that when given "ui:field": "SearchMultiTagSelectField" results in a searchable multi tag select field.

const schema = {
  properties: {
    enum: {
      type: "number",
      title: "Enum",
      enum: [1, 2, 3, 4],
    },
    enumOneOf: {
      type: "number",
      title: "Enum oneOf (non-standard)",
      oneOf: [
        { const: 1, title: "one" },
        { const: 2, title: "two" },
        { const: 3, title: "three" },
        { const: 4, title: "four" },
      ],
    },
    enumAnyOf: {
      type: "number",
      title: "Enum anyOf (non-standard)",
      anyOf: [
        { const: 1, title: "one" },
        { const: 2, title: "two" },
        { const: 3, title: "three" },
        { const: 4, title: "four" },
      ],
    },
    enumTagSelectAnyOf: {
      type: "string",
      title: "Enum searchable tag select anyOf",
      anyOf: [
        { const: 1, title: "one" },
        { const: 2, title: "two" },
        { const: 3, title: "three" },
        { const: 4, title: "four" },
      ],
    },
  },
};

const uiSchema = {
  enumTagSelectAnyOf: {
    "ui:field": "SearchMultiTagSelectField",
    "ui:options": {
      placeholder: "Search...",
      emptySearchMessage: "...no matching results found",
    },
  },
};

Alternative fields

There are alternative fields available for certain types. For type: "string" there is DateField and MultipleChoiceField.

DateField has a "dd.mm.yyyy" date input field and a calendar date picker and will save the date input as "yyyy-mm-dd" string.

const schema = {
  properties: {
    date: { type: "string", title: "Date" },
  },
};

const uiSchema = {
  date: { "ui:field": "DateField" },
};

MultipleChoiceField takes an array of oneOf or anyOf. The choices provided inside an oneOfarray will be presented on the UI as Radio button selectables and only one of the given values can be selected. The choices provided inside an anyOf array will be presented on the UI as Checkbox selectables and multiple values can be selected. MultipleChoiceField comes with a small number of predefined styles, which are inline, oneColumn, twoColumns and threeColumns. When no style is given, the choice options are listed in a row under the title.

const schema = {
  properties: {
    feeling: {
      type: "string",
      title: "How are you feeling today?",
      oneOf: [
        { const: "happy", title: "Happy" },
        { const: "creative", title: "Creative" },
        { const: "tired", title: "Tired" },
        { const: "nope", title: "Please don't even ask..." },
      ],
    },
  },
};

const uiSchema = {
  feeling: {
    "ui:field": "MultipleChoiceField",
    "ui:option": {
      style: "twoColumns",
    },
  },
};

For type: "boolean" there is RadioField and ToggleField. RadioField can take labels for the "true" and "false" options. If left empty, the default labels will be "true" and "false".

const schema = {
  properties: {
    question: { type: "boolean", title: "Are you ready?" },
    mode: { type: "boolean", title: "Toggle mode" },
  },
};

const uiSchema = {
  question: {
    "ui:field": "RadioField",
    "ui:label": { true: "yes", false: "no" },
  },
  mode: { "ui:field": "ToggleField" },
};

The alternative fields are customisable. The customisable elements are:

ToggleSwitchField.styles = {
  ToggleLabelStyle: ToggleLabel,
  ToggleFieldInputStyle: ToggleFieldInput,
  ToggleStyle: Toggle,
};

RadioButtonField.styles = {
  RadioButtonFieldContainerStyle: RadioButtonFieldContainer,
  RadioButtonFieldElementStyle: RadioButtonFieldElement,
  RadioButtonFieldInputStyle: RadioButtonFieldInput,
  ButtonLabelStyle: ButtonLabel,
  ButtonLabelTrueStyle: ButtonLabelTrue,
  ButtonLabelFalseStyle: ButtonLabelFalse,
};

Conditional fields

Conditional fields depending on a parent field input can be given with an if-then-else logic inside the schema making use of allOf. While ifand then are required to make the condition work, else is optional.

export const conditionsSchema = {
  type: "object",
  properties: {
    animal: {
      type: "string",
      oneOf: [
        { const: "cat", title: "Cat" },
        { const: "fish", title: "Fish" },
        { const: "dog", title: "Dog" },
      ],
    },
  },
  allOf: [
    {
      if: {
        properties: {
          animal: {
            const: "cat",
          },
        },
      },
      then: {
        properties: {
          food: {
            type: "string",
            enum: ["meat", "grass", "fish"],
          },
        },
        required: ["food"],
      },
    },
    {
      if: {
        properties: {
          animal: {
            const: "fish",
          },
        },
      },
      then: {
        properties: {
          food: {
            type: "string",
            enum: ["insect", "worms"],
          },
          water: {
            type: "string",
            enum: ["lake", "sea"],
          },
        },
        required: ["food", "water"],
      },
      else: {
        properties: {
          likesWalkiesWhere: {
            type: "string",
            enum: ["park", "forest", "garden"],
          },
        },
      },
    },

    {
      required: ["animal"],
    },
  ],
};

Ordering fields

The order of fields can be set using "ui:order": ["fieldName"] inside the uiSchema. If using "ui:order", all fields that should be visible in the form need to be included in the given array.

const schema = {
  properties: {
    secondField: { type: "string" },
    firstField: { type: "string" },
    thirdField: { type: "string" },
  },
};

const uiSchema = {
  "ui:order": ["firstField", "secondField", "thirdField"],
};

Props

Form component props:

<Form
  // Required
  schema={schema} // Form schema
  // Optional
  uiSchema={uiSchema} // Form UI schema
  className={"className"} // Form class name
  fields={fields} // Additional custom field components
  templates={templates} // Additional custom field templates
  components={components} // Override form components (e.g. wrappers) with custom ones
  formContext={formContext} // Form context
  useItemsAsAdditionalItems={true} // Allow "items" to be used as "additionalItems" in array fields that are missing "additionalItems", false by default
/>

Readme

Keywords

Package Sidebar

Install

npm i @nolwenture/formik-jsonschema-form

Weekly Downloads

27

Version

0.8.0

License

MIT

Unpacked Size

329 kB

Total Files

141

Last publish

Collaborators

  • rammohan.balasubramanian.nolwenture
  • antti.vikman
  • it_nolwenture
  • mika.kytojoki