question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Question: Is it possible to create reusable form sections?

See original GitHub issue

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.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:3
  • Comments:11 (3 by maintainers)

github_iconTop GitHub Comments

36reactions
billdybascommented, Jan 10, 2018

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?

2reactions
numicalcommented, Jan 8, 2019

+1

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to create reusable form components with React Hook ...
The first step is to create an input component. Creating an isolated component can be a good way to provide consumers of your...
Read more >
vue.js - How to create reusable form and input components in ...
You should create empty form with button (Cancel/Submit e.t.c) and add events for it. Then you should just pass inputs to slot and...
Read more >
Demonstrating Reusable React Components in a Form
To make this input element reusable in other places and projects, we'll have to extract it into its own component. Let's call it...
Read more >
Reducing the forms boilerplate — make your Angular forms ...
Reduce the forms boilerplate by creating reusable custom forms controls with the ControlValueAccessor interface. Learn how to create reusable forms, ...
Read more >
How to Make Fillable Forms Reusable in Word - YouTube
HOW TO MAKE FILLABLE FORMS REUSABLE IN WORD // For forms that need to be reused frequently, a best practice is to save...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found