Formik: Nested Forms

Created on 13 Aug 2018  Β·  29Comments  Β·  Source: formium/formik

Current Behavior

There doesn't appear to be official support for nested forms since the documentation doesn't mention this possibility. However, it does work; you just need your nested form to render a <div> rather than <form>, and ideally watch for the right events (e.g. Enter pressed in an input field) to autosubmit the sub form. Ultimately, the fields below the sub form get their state from the sub form's formik context rather than the parent form. Everything works great. But I'm hesitant to use this approach without official support because of the possibility of a change to Formik that breaks this nested form functionality.

Suggested Solution

Primarily, I just want the documentation to officially recognize this as a supported feature. A nice step beyond that would be to include a component that handles listening to the right kinds of events to simulate standard browser form submission.

Describe alternatives you've considered

FieldArrays can be used instead, but they require more work on the part of the user. For example, lets assume you want to submit an array of { firstName, lastName } objects to the server. Using FieldArrays, your Formik state will end up with { firstName: string[], lastName: string[] }, rather than simply { name: { firstName: string, lastName: string}[] }. Therefore, the user needs to implement some custom onSubmit logic to transform the formik values into the desired data structure.

Additionally, using FieldArrays instead makes your sub form less reusable. One could imagine an address subform that sometimes gets used as the main form for an entity that only has a single address, while other times gets used _within_ a field array for an entity that might have multiple address values.

Most helpful comment

@mellis481 I am very proud of the fact that I don't know things and that I don't have an ego about asking questions, even if they make me look silly or uninformed. I am actually familiar with the concept of nesting, just had not heard the terminology of "sub form."

However...
1) It's invalid HTML to nest <form> elements.
2) <Formik> uses React context and plain render props. To avoid name space conflicts with context with nesting you can just use props directly and access relevant variables within their scopes like so.

<Formik>
 {(formik) => (
    <form onSubmit={formik.handleSubmit}>
    <Formik>
       {(subformik) => (
          <form >
              <input name="username" onChange={subformik.handleChange} value={subformik.values.username} />
               {/** i also have access to formik here too */}
              <button onClick={() => subformik.submitForm()}>Submit Inner</button>
              <button onClick={() => formik.submitForm()}>Submit Outer</button>
          </form>
        )}
    </Formik>
    </form>
  )}
</Formik>

All 29 comments

I have discovered that I can namespace my field names further like address.0.address_line_1, resulting in the desired formik state ({ address: [{ address_line_1, ...}] }). However, I have to do a lot more wiring to make sure errors from the API propagate correctly. In fact, I ultimately found that I can't use form.errors because those are getting cleared out. Instead, I have to bake my own solution using form.status. It's workable, but it takes substantially more code on my end, and it relies heavily on somewhat opaque field name namespacing. It also doesn't let me leverage a lot of the custom stuff we have already built on top of Formik for our base form functionality (e.g. submit button loading states, disabled states, etc), nor does it offer the kind of reusability I mentioned in the last paragraph of my original post.

@ericbiewener would you mind sharing your solution for propagating those errors down to nested fields? Currently running into the same issue.

+1 for including error handling support with the namespace fields feature

What is a sub form? Can you give an example?

Ideally the developer would be able to nest formik forms as such:

<Formik>
 (formik) => (
    <Formik name="subform">(subformik) => (
        <input name="username" {...<props>}/>)
    </Formik>)
</Formik>

Which would result in something like this:

values = { 
    "subform": { 
        "username": ""
    }
}

Which is much more modular then the naming convention and would allow to reuse form parts. Problem is how to handle submit.

@YoungFaa this isn't what I meant, and it's actually already possible with formik. Just give your fields names like myNamespace.field1, myNamespace.field2

@jaredpalmer I'm referring to nesting <Formik> components inside of other <Formik> components. For example: https://codesandbox.io/s/42xxjrpz19

@ericbiewener there is an error in the nested form:
Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>

@zeflq right, that's why I noted in my original post that you need to render a div rather than a form for your nested form, and suggested that the ideal solution would "include a component that handles listening to the right kinds of events to simulate standard browser form submission."

What is a sub form? Can you give an example?

I don't mean to deride, but it's shocking to me that the creator of the go-to form library for React is unfamiliar with the concept of nested forms especially considering it's been dealt with in previous frameworks like Angular, etc.

@mellis481 I am very proud of the fact that I don't know things and that I don't have an ego about asking questions, even if they make me look silly or uninformed. I am actually familiar with the concept of nesting, just had not heard the terminology of "sub form."

However...
1) It's invalid HTML to nest <form> elements.
2) <Formik> uses React context and plain render props. To avoid name space conflicts with context with nesting you can just use props directly and access relevant variables within their scopes like so.

<Formik>
 {(formik) => (
    <form onSubmit={formik.handleSubmit}>
    <Formik>
       {(subformik) => (
          <form >
              <input name="username" onChange={subformik.handleChange} value={subformik.values.username} />
               {/** i also have access to formik here too */}
              <button onClick={() => subformik.submitForm()}>Submit Inner</button>
              <button onClick={() => formik.submitForm()}>Submit Outer</button>
          </form>
        )}
    </Formik>
    </form>
  )}
</Formik>

Sorry, I should have stuck with the terminology of "nested forms" -- that would have kept things clearer.

@jaredpalmer Right, what I described in my original post already works. That's why my suggested solution was really just about documenting that Formik could be used in this way. My concern was that nesting forms like this wasn't officially supported functionality and could therefore break in the future without warning. However, I can understand if you think this is an unnecessary thing for the docs to specify.

In regards to nesting <form> tags resulting in invalid HTML, that's why I also suggested that some kind of NestedForm component could be a nice addition to Formik. It would essentially render a div and have event handlers that replicate standard form submission functionality. Here's what I've written for my project:

class NestedForm extends Component<{ onSubmit: (SyntheticEvent<>) => void }> {
  onKeyDownCapture = (e: SyntheticKeyboardEvent<HTMLInputElement | HTMLButtonElement>) => {
    // Not bothering to handle `<input type="submit" />`
    if (e.key === "Enter") {
      if (
        e.target instanceof HTMLInputElement ||
        e.target instanceof HTMLSelectElement ||
        (e.target instanceof HTMLButtonElement && e.target.type !== "button")
      ) {
        e.preventDefault(); // Prevents outer form from submitting
        this.props.onSubmit(e);
      }
    } else if (e.key === "Space") {
      this.checkForSubmitButton(e);
    }
  };

  checkForSubmitButton = (e: SyntheticEvent<>) => {
    if (e.target instanceof HTMLButtonElement && e.target.type !== "button") {
      this.props.onSubmit(e);
    }
  };

  render(): Node {
    const { onSubmit, ...props } = this.props;

    return (
      <div onKeyDownCapture={this.onKeyDownCapture} onClick={this.checkForSubmitButton} role="form" {...props} />
    );
  }
}

@ericbiewener As you've just shown, this seems like it can be implemented in user land as its implementation is very specific to the desired UX. Would be happy to add the published package to the resources tab of the docs once it's out.

Yup, that sounds like the right approach rather than bloating your library :)

I see the issue here being Formik use of Context API, we might improve and maybe even namespace it.

Imagine you have one big form and then a popup appears which has another form, that is completely unrelated and submits to different API.

Since the popup could be implemented by using React.Portals which shares the context, or the popup could be just a position fixed div with high z-index, the form in the popup will trigger the formik in the first level.

One solution would be for every Formik to override the previous context and create a new.

It does override the context of the outer Formik form already. As I
mentioned in the OP, the nested form approach already works fine.

On Mon, Jan 21, 2019, 5:36 AM Gaston Morixe <[email protected] wrote:

I see the issue here being Formik use of Context API, we might improve and
maybe even namespace it.

Imagine you have one big form and then a popup appears which has another
form, that is completely unrelated and submits to different API.

Since the popup could be implemented by using React.Portals which shares
the context, or the popup could be just a position fixed div with high
z-index, the form in the popup will trigger the formik in the first level.

One solution would be for every Formik to override the previous context
and create a new one so it doesn't pollute the context down the tree.

Another might be also namespacing the contexts, just for safety. Since the
connected Fields should keep not knowing about the context name, but just
using the nearest context.

β€”
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/jaredpalmer/formik/issues/826#issuecomment-456076377,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAPdcuBe_m7NsbzE1DQRUyj0BApMyVbxks5vFcJagaJpZM4V7CTc
.

Yes sorry, just realized this today. There might still a problem with the an html button submit type, it submits both forms.

Using a normal button with the onClick only to the right Formik works fine.

Best,

Gaston

Sent from my iPhone

On Jan 21, 2019, at 1:26 PM, ericbiewener notifications@github.com wrote:

It does override the context of the outer Formik form already. As I
mentioned in the OP, the nested form approach already works fine.

On Mon, Jan 21, 2019, 5:36 AM Gaston Morixe <[email protected] wrote:

I see the issue here being Formik use of Context API, we might improve and
maybe even namespace it.

Imagine you have one big form and then a popup appears which has another
form, that is completely unrelated and submits to different API.

Since the popup could be implemented by using React.Portals which shares
the context, or the popup could be just a position fixed div with high
z-index, the form in the popup will trigger the formik in the first level.

One solution would be for every Formik to override the previous context
and create a new one so it doesn't pollute the context down the tree.

Another might be also namespacing the contexts, just for safety. Since the
connected Fields should keep not knowing about the context name, but just
using the nearest context.

β€”
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/jaredpalmer/formik/issues/826#issuecomment-456076377,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAPdcuBe_m7NsbzE1DQRUyj0BApMyVbxks5vFcJagaJpZM4V7CTc
.

β€”
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

Using the subformik approach above, I'm seeing a warning in react-dom.development.js (line 518):

Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>. in form (created by Formik)

@mellis481 see this comment

Using the subformik approach above, I'm seeing a warning in react-dom.development.js (line 518):

Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>. in form (created by Formik)

This is nothing but React telling you YOU are nesting HTML elements in unpredictable way. The unfortunate is Form is rendered by Formik, thus easiest thing is to blame Formik.

"Nesting" can be done

  • using portals and not nesting actually. DOM tree is not React component tree
  • Not using form elements, rather explicitly using formik.submitForm to submit "virtual" form.

Hello there!

I know this is a closed issue, but I've had the same problem regard "subforms" as you did (or do).
In the last few months I've been working a lot with forms and I thought to write an article about my personal experience.

In the article I explain how I've decided to organise the forms in the application in order to maximise reusability, flexibility and scalability.

That is just the best way I've found to deal with subforms in my use cases, so you may found it useless.
Nevertheless, I hope to add some kind of value to the next person that reads this issue, giving at least one possible solution to deal with subforms

I hope not to offend anyone publishing the link to the article in here, and if I do I apologise.
https://auto1.tech/the-concept-of-subforms-with-formik/

@NicholasPeretti
https://auto1.tech/the-concept-of-subforms-with-formik/

I've tried to create sub-forms according to your post, had problem to access the variable for the subforms. Maybe this is a stupid question but please bear with me. Let say i wanted to check the value in the subform, how would one do that?

Simplified Example:

Condition syntax

[props.namespaces][FIRSTNAME]  && (
        <Field component="input" name={withNamespace(LAST_NAME)} />
        <ErrorMessage name={withNamespace(FIRST_NAME)} />
)}

Hi @diehell, thanks for reaching out.

As default approach to this I'd use the get from lodash. Since the withNamespace function just joins the namespace and the field name with a dot (.) it's possible to use the lodash function to get and set values down the tree. Let me make it clearer with some examples

Subform case

If you need to get the value in your subform you could just use a custom render method. If you pass a function as render prop it will give you also the value as param, so your code should look like this:

<Field name={withNamespace(FIRST_NAME)} render={({ field }) => (
        <input type="text" name={withNamespace(LAST_NAME)} {...field} />
        <ErrorMessage name={withNamespace(FIRST_NAME)} />
)} />

The field param contains the value property, so if you want to do additional logic on that field you just need to access to the property

{field.value < 10 && (
  //  ...
)}

Main form case

You may need to access to your subforms data from your main form. In this component you'll have the values variable provided by <Formik />. From there you can use the lodash.get approach to get the values you want from the values tree:

<Formik 
  // ...
  render={({ values }) => (
    // ...
    {get(values, withNamespace(FIRST_NAME)) && (
      <Field component="input" name={withNamespace(LAST_NAME)} />
      <ErrorMessage name={withNamespace(FIRST_NAME)} />
    )}
    // ...
  )}
/>

I've tried to do my best to show you how I'd do it. Anyway I don't know why you need to get the value of the field and neither where you need to get it (main form, subform...).
For this reason I'm not sure I've been helpful.

If you could add some more detail about your use-case I'm sure I could give you some more hint and this could be useful also to the future users that will read this issue.

Feel free to get in touch with me for any further question πŸ˜„

Working off of what @jaredpalmer did here I got nested inputs working and populating the outer forms values.

Result on submit

note it populated the sub-properties of homeAddress:

{
  "firstName": "Flavio",
  "lastName": "Espinoza",
  "email": "[email protected]",
  "mobilePhone": "650-695-6911",
  "homeAddress": {
    "type": "home",
    "street_address": "1538 S 400 E",
    "city": "Salt Lake City",
    "state": "UT",
    "zipcode": "84115"
  }
}

React component

import * as React from 'react';
import _ from 'lodash';
import { Formik, Field } from 'formik';
import { Button, Grid } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles((theme) => ({
  container: {
    display: 'flex',
    flexWrap: 'wrap',
  },
}));

const NestedForm = () => {
  const classes = useStyles();
  return (
    <Formik
      initialValues={{
        firstName: '',
        lastName: '',
        email: '',
        mobilePhone: '',
        homeAddress: {
          type: 'home',
          street_address: '',
          city: '',
          state: '',
          zipcode: '',
        },
      }}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 500);
      }}>
      {(formik) => {
        return (
          <Grid justify="center" className={'pl24 pr24'}>
              <h4>Required</h4>
              <Field
                name="firstName"
                className={'mb24 mt12'}
                onChange={formik.handleChange}
                placeholder={'First name'}
              />
              <Field
                name="lastName"
                className={'mb24 mt12'}
                onChange={formik.handleChange}
                placeholder={'Last name'}
              />
              <Field
                name="email"
                className={'mb24 mt12'}
                onChange={formik.handleChange}
                placeholder={'Email'}
              />
              <Field
                name="mobilePhone"
                className={'mb24 mt12'}
                onChange={formik.handleChange}
                placeholder={'Mobile phone'}
              />
              <br />
              <Formik className={classes.container}>
                {(subformik) => {
                  return (
                    <section>
                      <div>
                        <h4>Home address</h4>
                        <Field
                          name="homeAddress.street_address"
                          className={'mb24 mt12'}
                          onChange={formik.handleChange}
                          placeholder={'street address'}
                        />
                        <Field
                          name="homeAddress.city"
                          className={'mb24 mt12'}
                          onChange={formik.handleChange}
                          placeholder={'city'}
                        />
                        <Field
                          name="homeAddress.state"
                          className={'mb24 mt12'}
                          onChange={formik.handleChange}
                          placeholder={'state'}
                        />
                        <Field
                          name="homeAddress.zipcode"
                          className={'mb24 mt12'}
                          onChange={formik.handleChange}
                          placeholder={'zipcode'}
                        />
                      </div>
                      <Button
                        variant="contained"
                        color="primary"
                        className={classes.button}
                        onClick={() => formik.handleSubmit()}>
                        Submit
                      </Button>
                    </section>
                  );
                }}
              </Formik>
          </Grid>
        );
      }}
    </Formik>
  );
};

export default NestedForm;

@flavioespinoza just want to point out that you do not necessarily need a "subform" here, instead you can use a custom field which accepts a value of your object and handles that slice of your state as if it is a single field.

const myForm = () => (
    <Field component={AddressField} name="homeAddress" />
);

const AddressField = ({name: parentFieldName}) => {
    const parentField = useField(parentFieldName); // v2, but you can get this from v1 as well
    const handleChange = useCallback(event => formik.handleChange(parentFieldName)({
        ...parentField.value,
        [event.target.name]: event.target.value
    ), [parentFieldName, parentField.value]);

    return <fieldset className="input--address">
        <Field
          name={`${parentFieldName}.street_address`}
          className={'mb24 mt12'}
          onChange={handleChange}
          placeholder={'street address'}
        />
        {/* etc */}
    </fieldset>
}

There are a million ways you can expand on this, but this is how I usually handle this situation.

Hello there!

I know this is a closed issue, but I've had the same problem regard "subforms" as you did (or do).
In the last few months I've been working a lot with forms and I thought to write an article about my personal experience.

In the article I explain how I've decided to organise the forms in the application in order to maximise reusability, flexibility and scalability.

That is just the best way I've found to deal with subforms in my use cases, so you may found it useless.
Nevertheless, I hope to add some kind of value to the next person that reads this issue, giving at least one possible solution to deal with subforms

I hope not to offend anyone publishing the link to the article in here, and if I do I apologise.
https://auto1.tech/the-concept-of-subforms-with-formik/

@NicholasPeretti thanks for the post, we used this pattern in our app and it helped us structure our big forms in a more componentized way. I created a storybook addon to help maintain forms in this way so that you can play with subforms directly in storybook and see the formik state in a panel. I hope it helps with others using this pattern πŸ‘

https://www.npmjs.com/package/storybook-formik

Hi @bhishp ! Thank you for your feedback!
I'm very happy that subforms worked out for your team!

Months passed by since I wrote the article and, in the meantime, I've been able to improve the way I am using subforms.

If you'd like to read more about it, I've also given a talk (unfortunately not available online).
You can find the slides here: https://docs.google.com/presentation/d/1MgdcKdsj7eIbt3NFiDq2hxnszFTP5WF60nu0TWiaDvg/edit#slide=id.p

There's also a little app that I've used as example that can be very helpful, you can find it here: https://github.com/NicholasPeretti/subforms-example

I would really like to hear what were your struggles with subforms (if any) and how you and your team solved them!
I've found some flaws in these months, like how to scale with validation.

As you can see from the Github repo I've started to export a function that returns an object instead of the validation schema directly.
This allow you to have two subforms at the root of the form, like this:

const rootValidationSchema = yup.shape({
  ...subForm1.validationSchema(),
  ...subForm2.validationSchema().
})

Please take a look at the codebase if you want to read more about it! ☺️

what if i have nested forms and need a 'isValid' prop and an 'errors' prop for each form?

@JaredDahlke you can manage these in userland like

const isValid = childForm.isValid && parentForm.isValid;
// this is oversimplified. you'll want to memoize, and 
// if you have nested values like `{ name: { first: '' } }`, you'll need a deep merge.
const errors = { ...childForm.errors, ...parentForm.errors };

Guys I've been able to implement formik on all of my forms except the wizard/nested form. I've made multiple attempts but have been unable to update the master formik from within a child formik. Here is an example of what I am trying to do. Any help/pointers would be great. I am not married to this structure , but i do need live/real time validation on each sub form with the user having to click any button, needs to validate on input. The Wizard needs to know the validation status of the 2 sub components. This is a simplified example of what im trying to build for my company:

 //Wizard.js

 import React from 'react';
 import { Formik, Form, Field } from 'formik';
 import * as Yup from 'yup';

let defaultEmail = '[email protected]'
let defaultLastName = 'Smith'

export default function WizardHome(props) {

const [activeComponent, setActiveComponent] = React.useState(0)

const handleChangeComponent=()=>{
  if(activeComponent === 0) {
    setActiveComponent(1)
  } else {
    setActiveComponent(0)
  }
}


  return (
    <Formik
      initialValues={{
        componentOneIsValid: false,
        componentTwoIsValid: false            
      }}
    > 
    {formik => (
    <div>   
        <div>Component 1 is valid?: {formik.values.componentOneIsValid ? 'True' : 'False'}</div> <br/>
        <div>Component 2 is valid?: {formik.values.componentTwoIsValid ? 'True' : 'False'}</div> 
          {activeComponent === 0 ?
          <div>         
            <ComponentOne masterFormik={formik}/>            
          </div>
            : 
          <div>         
            <ComponentTwo masterFormik={formik}/>            
          </div>

          }
          <button onClick={handleChangeComponent()}>Change Component</button>

    </div>

    )}

    </Formik>

  )
}


//ComponentOne.js

import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';

const componentOneSchema = Yup.object().shape({
 lastName: Yup.string()
   .min(2, 'Too Short!')
   .max(50, 'Too Long!')
   .required('Required'),
 email: Yup.string().email('Invalid email').required('Required'),
});

export default  ComponentOne =(props)=>{

  if (formikOne.isValid) {   //I know I don't have access to this here, but how do i get it?
    props.masterFormik.values.componentOneIsValid = true  //I don't think it lets me set this from here, how can I do?
  } 

  return (
    <Formik
      initialValues={{
        email: defaultEmail,
        lastName: defaultLastName            
      }}
      validationSchema={componentOneSchema}
    > 
    {formikOne => (
      <div>    
        <Field name="email"/>
        <Field name="lastName"/>                
      </div>

    )}

    </Formik>
  )
}


//ComponentTwo.js

  //... same as component one but with different fields

I put together a NestedForm component for Formik using some of the snippets in this thread. Here you go:

import { Formik, FormikProps } from 'formik';
import React, { isValidElement } from 'react';

interface Props<Values> {
  initialValues: Values;
  onSubmit: (values: Values) => void;
  children?: ((props: FormikProps<Values>) => any) | any;
}

function NestedForm<Values>({
  initialValues,
  onSubmit,
  children,
}: Props<Values>) {
  function onKeyDownCapture(form: FormikProps<Values>) {
    return (e: any) => {
      if (e.key === 'Enter') {
        if (
          e.target instanceof HTMLInputElement ||
          e.target instanceof HTMLSelectElement ||
          (e.target instanceof HTMLButtonElement && e.target.type !== 'button')
        ) {
          e.preventDefault(); // Prevents outer form from submitting
          onSubmit(form.values);
        }
      } else if (e.key === 'Space') {
        checkForSubmitButton(e);
      }
    };
  }

  function checkForSubmitButton(form: FormikProps<Values>) {
    return (e: any) => {
      if (e.target instanceof HTMLButtonElement && e.target.type !== 'button') {
        onSubmit(form.values);
      }
    };
  }

  return (
    <Formik initialValues={initialValues} onSubmit={() => null}>
      {form => (
        <div onKeyDownCapture={onKeyDownCapture(form)} onClick={checkForSubmitButton(form)}>
          {isValidElement(children) ? children : children(form)}
        </div>
      )}
    </Formik>
  );
}

Use like a normal Formik form:

<NestedForm initialValues={values} onSubmit={handleSubmit}>
  ...
</NestedForm>

Just extend it if you want validation schemas etc.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

PeerHartmann picture PeerHartmann  Β·  3Comments

jaredpalmer picture jaredpalmer  Β·  3Comments

green-pickle picture green-pickle  Β·  3Comments

jaredpalmer picture jaredpalmer  Β·  3Comments

Jucesr picture Jucesr  Β·  3Comments