Formik: Question: Is it possible to create reusable form sections?

Created on 3 Jan 2018  路  11Comments  路  Source: formium/formik

Question

Is it possible to create reusable form sections w/ Formik? Or nest Formik components?

In redux-form, there is the concept of a FormSection.

For example, something like:

class MyFormSection extends Component {
  return (
    <Formik 
      initialValues={{
        a: '',
        b: ''
      }}
      validationSchema={() => yup.object()
        .shape({
          a: yup.string(),
          b: yup.string()
        })
      }
      render={({
        values,
        errors,
        touched,
        handleChange,
        handleBlur
      }) => (
        <fieldset>
          <input name="a" onChange={handleChange} onBlur={handleBlur} value={values.a} />
          {touched.a && errors.a && <div>{errors.a}</div>}
          <input name="b" onChange={handleChange} onBlur={handleBlur} value={values.b} />
          {touched.b && errors.b && <div>{errors.b}</div>}
        </fieldset>
      )}
    />
  )
}

class MyEntireForm extends Component {
  return (
    <Formik
      initialValues={{
        c: ''
      }}
      validationSchema={() => yup.object()
        .shape({
          c: yup.string()
        })
      }
      onSubmit={(values) => {
        console.log(values) // { c: '' }
      }}
      render={({
        values,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit
      }) => (
        <form onSubmit={handleSubmit}>
           <MyFormSection />
           <input name="c" onChange={handleChange} onBlur={handleBlur} value={values.c} />
           {touched.c && errors.c && <div>{errors.c}</div>}
           <button type="submit">Submit</button>
        </form>
      )}
    />
  )
}

I started going down this route, but I'm unsure how MyFormSection would know how to be submitted or how MyEntireForm would receive the submitted values from MyFormSection.

User Land Wontfix Question

Most helpful comment

Had to use quasi-inheritance rather than composition. Isn't as flexible as self-validating fieldsets but works.

For any future Internet travelers, this was my solution:

class MyFormSection extends Component {
  static propTypes = {
    initialValues: PropTypes.object.isRequired,
    validationSchema: PropTypes.object.isRequired,
    onSubmit: PropTypes.func.isRequired,
    render: PropTypes.func.isRequired
  }

  render () {
    const {
      initialValues,
      validationSchema,
      onSubmit,
      render
    } = this.props

    return (
      <Formik 
        initialValues={{ ...initialValues, a: '', b: '' }}
        validationSchema={yup.object()
          .shape({
            a: yup.string(),
            b: yup.string()
          })
          .concat(validationSchema) // Could also verify 'validationSchema' is a proper yup schema
        }
        onSubmit={(values) => {
          onSubmit(values) // Transform 'values' as needed and pass them along
        }}
        render={({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          handleSubmit
        }) => (
          <form onSubmit={handleSubmit}>
            <fieldset>
              <input name="a" onChange={handleChange} onBlur={handleBlur} value={values.a} />
              {touched.a && errors.a && <div>{errors.a}</div>}
              <input name="b" onChange={handleChange} onBlur={handleBlur} value={values.b} />
              {touched.b && errors.b && <div>{errors.b}</div>}
            </fieldset>
            {render({ // Pass along any Formik props you intend to use in the other section
              values,
              errors,
              touched,
              handleChange,
              handleBlur,
              isSubmitting
            })}
          </form>
        )}
      />
    )
  }
}

class MyEntireForm extends Component {
  render () {
    return (
      <MyFormSection
        initialValues={{ c: '' }}
        validationSchema={yup.object().shape({ c: yup.string() })}
        onSubmit={(values) => {
          console.log(values) // { a: '', b: '', c: '' }
        }}
        render={({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          isSubmitting
        }) => (
          <Fragment>
            <input name="c" onChange={handleChange} onBlur={handleBlur} value={values.c} />
            {touched.c && errors.c && <div>{errors.c}</div>}
            <button type="submit" disabled={isSubmitting}>Submit</button>
          </Fragment>
        )}
      />
    )
  }
}

The difficulty right now is in being able to reuse validation logic in fieldsets which appear in multiple forms (but each form has additional other fields). Formik appears to have the requirement that there needs to be <form onSubmit={handleSubmit}> somewhere inside its render prop which means you can't properly render a Formik component inside another Formik component because you can't nest forms.

What would be neat to have is something like a Formik Fieldset, essentially a Formik component that doesn't need a <form> inside its render prop but expects to be used in a Formik component that does:

class FieldsetOne extends Component {
  render () {
    return (
      <Fieldset
        initialValues={{ a: '', b: '' }}
        validationSchema={yup.object().shape({ a: yup.string(), b: yup.string() })}
        render={({ errors, touched }) => (
          <fieldset>
            <Field name="a" />
            {touched.a && errors.a && <div>{errors.a}</div>}
            <Field name="b" />
            {touched.b && errors.b && <div>{errors.b}</div>}
          </fieldset>
        )}
      />
    )
  }
}

class FieldsetTwo extends Component {
  render () {
    return (
      <Fieldset // A version that uses children instead of a render prop
        initialValues={{ c: '', d: '' }}
        validationSchema={yup.object().shape({ c: yup.string(), d: yup.string() })}
      >
        {({ errors, touched }) => (
          <fieldset>
            <Field name="c" />
            {touched.c && errors.c && <div>{errors.c}</div>}
            <Field name="d" />
            {touched.d && errors.d && <div>{errors.d}</div>}
          </fieldset>
        )}
      </Fieldset>
    )
  }
}

class MyForm extends Component {
  render () {
    return (
      <Formik
        initialValues={{ e: '' }}
        validationSchema={yup.object().shape({ e: yup.string() })}
        onSubmit={(values) => {
          console.log(values) // {a: '', b: '', c: '', d: '', e: '', f: ''}
        }}
        render={({ errors, touched, handleSubmit }) => (
          <form onSubmit={handleSubmit}>
            <FieldsetOne />
            <Field name="e" />
            {touched.e && errors.e && <div>{errors.e}</div>}
            <FieldsetTwo />
            <Fieldset // You'd probably refactor this into the Formik component itself, but included to show how this component could be used
              initialValues={{ f: '' }}
              validationSchema={yup.object().shape({ f: yup.string() })}
              render={({ errors, touched }) => (
                <fieldset>
                  <Field name="f" />
                  {touched.f && errors.f && <div>{errors.f}</div>}
                </fieldset>
              )}
            />
          </form>
        )}
      />
    )
  }
}

where FieldsetOne and FieldsetTwo can be moved around, added / removed as needed, used in other Formik forms, and the Formik component combines their initialValues and validationSchema with its own, and onSubmit combines all the sections' values together.

I don't know if this is even possible, but my mental model thinks that this could be useful. Perhaps it could be achieved another way?

Thoughts?

All 11 comments

You can do this with <Field/> or just pass down props.

For example...

const Address = () => 
    <div>
       <Field name="street1" />
       <Field name="street2" />
       <Field name="city" /> 
       <Field name="state" />
       <Field name="postal" /> 
    </div>

I'm sorry if it wasn't clear, but my use case is having a particular validationSchema attached w/ the fields in MyFormSection, therefore, not having to duplicate that validation code every time I want to use MyFormSection.

Think self-validating fieldset building blocks.

Had to use quasi-inheritance rather than composition. Isn't as flexible as self-validating fieldsets but works.

For any future Internet travelers, this was my solution:

class MyFormSection extends Component {
  static propTypes = {
    initialValues: PropTypes.object.isRequired,
    validationSchema: PropTypes.object.isRequired,
    onSubmit: PropTypes.func.isRequired,
    render: PropTypes.func.isRequired
  }

  render () {
    const {
      initialValues,
      validationSchema,
      onSubmit,
      render
    } = this.props

    return (
      <Formik 
        initialValues={{ ...initialValues, a: '', b: '' }}
        validationSchema={yup.object()
          .shape({
            a: yup.string(),
            b: yup.string()
          })
          .concat(validationSchema) // Could also verify 'validationSchema' is a proper yup schema
        }
        onSubmit={(values) => {
          onSubmit(values) // Transform 'values' as needed and pass them along
        }}
        render={({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          handleSubmit
        }) => (
          <form onSubmit={handleSubmit}>
            <fieldset>
              <input name="a" onChange={handleChange} onBlur={handleBlur} value={values.a} />
              {touched.a && errors.a && <div>{errors.a}</div>}
              <input name="b" onChange={handleChange} onBlur={handleBlur} value={values.b} />
              {touched.b && errors.b && <div>{errors.b}</div>}
            </fieldset>
            {render({ // Pass along any Formik props you intend to use in the other section
              values,
              errors,
              touched,
              handleChange,
              handleBlur,
              isSubmitting
            })}
          </form>
        )}
      />
    )
  }
}

class MyEntireForm extends Component {
  render () {
    return (
      <MyFormSection
        initialValues={{ c: '' }}
        validationSchema={yup.object().shape({ c: yup.string() })}
        onSubmit={(values) => {
          console.log(values) // { a: '', b: '', c: '' }
        }}
        render={({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          isSubmitting
        }) => (
          <Fragment>
            <input name="c" onChange={handleChange} onBlur={handleBlur} value={values.c} />
            {touched.c && errors.c && <div>{errors.c}</div>}
            <button type="submit" disabled={isSubmitting}>Submit</button>
          </Fragment>
        )}
      />
    )
  }
}

The difficulty right now is in being able to reuse validation logic in fieldsets which appear in multiple forms (but each form has additional other fields). Formik appears to have the requirement that there needs to be <form onSubmit={handleSubmit}> somewhere inside its render prop which means you can't properly render a Formik component inside another Formik component because you can't nest forms.

What would be neat to have is something like a Formik Fieldset, essentially a Formik component that doesn't need a <form> inside its render prop but expects to be used in a Formik component that does:

class FieldsetOne extends Component {
  render () {
    return (
      <Fieldset
        initialValues={{ a: '', b: '' }}
        validationSchema={yup.object().shape({ a: yup.string(), b: yup.string() })}
        render={({ errors, touched }) => (
          <fieldset>
            <Field name="a" />
            {touched.a && errors.a && <div>{errors.a}</div>}
            <Field name="b" />
            {touched.b && errors.b && <div>{errors.b}</div>}
          </fieldset>
        )}
      />
    )
  }
}

class FieldsetTwo extends Component {
  render () {
    return (
      <Fieldset // A version that uses children instead of a render prop
        initialValues={{ c: '', d: '' }}
        validationSchema={yup.object().shape({ c: yup.string(), d: yup.string() })}
      >
        {({ errors, touched }) => (
          <fieldset>
            <Field name="c" />
            {touched.c && errors.c && <div>{errors.c}</div>}
            <Field name="d" />
            {touched.d && errors.d && <div>{errors.d}</div>}
          </fieldset>
        )}
      </Fieldset>
    )
  }
}

class MyForm extends Component {
  render () {
    return (
      <Formik
        initialValues={{ e: '' }}
        validationSchema={yup.object().shape({ e: yup.string() })}
        onSubmit={(values) => {
          console.log(values) // {a: '', b: '', c: '', d: '', e: '', f: ''}
        }}
        render={({ errors, touched, handleSubmit }) => (
          <form onSubmit={handleSubmit}>
            <FieldsetOne />
            <Field name="e" />
            {touched.e && errors.e && <div>{errors.e}</div>}
            <FieldsetTwo />
            <Fieldset // You'd probably refactor this into the Formik component itself, but included to show how this component could be used
              initialValues={{ f: '' }}
              validationSchema={yup.object().shape({ f: yup.string() })}
              render={({ errors, touched }) => (
                <fieldset>
                  <Field name="f" />
                  {touched.f && errors.f && <div>{errors.f}</div>}
                </fieldset>
              )}
            />
          </form>
        )}
      />
    )
  }
}

where FieldsetOne and FieldsetTwo can be moved around, added / removed as needed, used in other Formik forms, and the Formik component combines their initialValues and validationSchema with its own, and onSubmit combines all the sections' values together.

I don't know if this is even possible, but my mental model thinks that this could be useful. Perhaps it could be achieved another way?

Thoughts?

I realize this got closed a while ago, but is there something similar to the proposed solution @billdybas that has been built into formik? I would love to be able to compose my forms like that example.

I am also attempting to implement something similar, and would love to see something in the official release!

Same here.

same here, as for DRY I was looking for a way to have reusable fields snippets with their own logic like the example above

+1

+1

Adding wontfix and userland labels. React's existing component model is more explicit and will be more performant as this will require context.

Was this page helpful?
0 / 5 - 0 ratings