5

Dynamic and recursive forms with Formik and TypeScript

 2 years ago
source link: https://wanago.io/2022/05/09/dynamic-recursive-forms-formik-typescript-react/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Dynamic and recursive forms with Formik and TypeScript

React

May 9, 2022

Formik is a popular tool that helps us write React forms and keep their code consistent across even big projects. Besides having a fixed number of properties, we sometimes need to allow the user to shape the form to some extent and create recursive data structures. In this article, we learn how to do that with Formik and build the following form:

form.gif

Defining the basics of our form

Let’s start with defining our data structure. The crucial thing about our form is that it is recursive.

If you want to know more about recursion, check out Using recursion to traverse data structures. Execution context and the call stack

DynamicFormProperty.tsx
interface DynamicFormProperty {
  id: string;
  label: string;
  properties: DynamicFormProperty[];
export default DynamicFormProperty;

We add the id property above to use with the key prop.

First, we need to create the entry point that uses the <Formik /> component to initialize our form.

DynamicForm.tsx
import React from 'react';
import { Formik, Form } from 'formik';
import useDynamicForm from './useDynamicForm';
import FormProperty from './FormProperty/FormProperty';
import styles from './DynamicForm.module.scss';
const DynamicForm = () => {
  const { handleSubmit, initialValues } = useDynamicForm();
  return (
    <Formik onSubmit={handleSubmit} initialValues={initialValues}>
      <Form className={styles.form}>
        <FormProperty />
        <button className={styles.submitButton} type="submit">
          Submit
        </button>
      </Form>
    </Formik>
export default DynamicForm;

Above, I’m using SCSS modules to style this simple component.

We initiate the form with simple values for this demonstration and log them when the user clicks on the submit button.

useDynamicForm.tsx
import DynamicFormProperty from './DynamicFormProperty';
function useDynamicForm() {
  const initialValues: DynamicFormProperty = {
    label: '',
    properties: [],
  const handleSubmit = (values: DynamicFormProperty) => {
    console.log(values);
  return {
    initialValues,
    handleSubmit,
export default useDynamicForm;

Creating an elementary text input

In this article, we create a form that has text inputs. Because of that, we need to create a component to handle them.

TextInput.tsx
import React, { FunctionComponent } from 'react';
import { useField } from 'formik';
import styles from './TextInput.module.scss';
interface Props {
  name: string;
const TextInput: FunctionComponent<Props> = ({ name }) => {
  const [field] = useField({ name });
  return <input {...field} className={styles.input} />;
export default TextInput;

Above, we use the useField function to interact with Formik. If you want to know more, go to Building forms using Formik with the React Hooks API and TypeScript

Our TextInput is very simple to use. We need to provide it with the path to a form field we want it to use.

<TextInput name="label" />
<TextInput name="properties[0].label" />

Accessing nested properties of the form

To access a nested property of an object, we can use the getIn function provided by Formik. It returns a value based on the provided path.

The getIn function works very similarly to the get function provided by Lodash.

import { getIn } from 'formik';
import DynamicFormProperty from '../DynamicFormProperty';
const values: DynamicFormProperty = {
  id: 'root',
  label: 'root',
  properties: [
      label: 'second-level-1',
      id: '2aa571ca-0b65-43ad-8309-24786f07c795',
      properties: [],
      label: 'second-level-2',
      id: '9717c75f-1421-4518-a1fc-b404d1da001c',
      properties: [
          label: 'third-level-1',
          id: 'f1645fc1-1dac-4fa9-8b8a-d4427fbb9fec',
          properties: [],
const thirdLevelLabel = getIn(values, 'properties[1].properties[0].label');
console.log(thirdLevelLabel); // third-level-1

Since we need a full path to access a particular property, we have to keep track of how deep we are in our form. Because of that, our FormProperty has the prefix property.

FormProperty.tsx
import React, { FunctionComponent } from 'react';
import TextInput from '../TextInput/TextInput';
import useFormProperty from './useFormProperty';
import styles from './FormProperty.module.scss';
interface Props {
  prefix?: string;
const FormProperty: FunctionComponent<Props> = ({ prefix = '' }) => {
  const { properties, addNewProperty, removeProperty } =
    useFormProperty(prefix);
  return (
    <div className={styles.wrapper}>
      <div className={styles.labelContainer}>
        <TextInput name={`${prefix}label`} />
        <button
          type="button"
          onClick={addNewProperty}
          className={styles.addPropertyButton}
        </button>
      </div>
      {properties.map((property, index) => (
        <div className={styles.propertyContainer}>
          {property.id !== 'root' && (
            <button
              type="button"
              onClick={removeProperty(index)}
              className={styles.removePropertyButton}
            </button>
          <FormProperty
            key={property.id}
            prefix={`${prefix}properties[${index}].`}
          />
        </div>
    </div>
export default FormProperty;

Some of the possible values for the prefix prop that the FormProperty uses are the following:

  • an empty string
  • properties[0].
  • properties[1].
  • properties[1].properties[0].
  • properties[1].properties[0].properties[0].

To access the array of properties, we need to get the form values first. To do that, we can use the useFormikContext hook.

const { values } = useFormikContext();

Paired with the getIn, we can use the above to access a particular array of properties.

const properties: DynamicFormProperty[] = getIn(
  values,
  `${prefix}properties`,

The crucial thing to notice above is that we use the index value when mapping the array of properties. Thanks to that, we can set the correct prefix.

<FormProperty
  key={property.id}
  prefix={`${prefix}properties[${index}].`}
/>

Modifying the array of properties

To add or remove properties, we need to modify the right properties array.  To do that, we can use the setFieldValue function provided by the useFormikContext hook. It uses the path of the property in the same way the getIn function does.

Adding properties

We can use the uuid library to generate unique ids when adding a new property. To do that, we can install it with npm.

npm install uuid @types/uuid
useFormProperty.tsx
import { useFormikContext, getIn } from 'formik';
import DynamicFormProperty from '../DynamicFormProperty';
import { v4 as uuid } from 'uuid';
function useFormProperty(prefix: string) {
  const { values, setFieldValue } = useFormikContext();
  const properties: DynamicFormProperty[] = getIn(
    values,
    `${prefix}properties`,
  const addNewProperty = () => {
    const newProperty: DynamicFormProperty = {
      label: '',
      id: uuid(),
      properties: [],
    const newProperties = [...properties, newProperty];
    setFieldValue(`${prefix}properties`, newProperties);
  // ...
  return {
    properties,
    addNewProperty,
    removeProperty,
export default useFormProperty;

Removing properties

We can delete an element from the array using a given index to remove a property. When put together, our hook likes like that:

useFormProperty.tsx
import { useFormikContext, getIn } from 'formik';
import DynamicFormProperty from '../DynamicFormProperty';
import { v4 as uuid } from 'uuid';
function useFormProperty(prefix: string) {
  const { values, setFieldValue } = useFormikContext();
  const properties: DynamicFormProperty[] = getIn(
    values,
    `${prefix}properties`,
  const addNewProperty = () => {
    const newProperty: DynamicFormProperty = {
      label: '',
      id: uuid(),
      properties: [],
    const newProperties = [...properties, newProperty];
    setFieldValue(`${prefix}properties`, newProperties);
  const removeProperty = (propertyIndex: number) => () => {
    const newProperties = [...properties];
    newProperties.splice(propertyIndex, 1);
    setFieldValue(`${prefix}properties`, newProperties);
  return {
    properties,
    addNewProperty,
    removeProperty,
export default useFormProperty;

Summary

In this article, we’ve gone through the idea of creating dynamic, recursive forms with Formik and TypeScript. When doing that, we had to use various hooks provided by Formik to access and modify the data directly. We’ve also used the getIn that Formik exposes, but it is not thoroughly documented. That knowledge helped us create a fully functional flow that allows the user to add and remove fields.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK