Search code examples
reactjsformikyupnested-objectionic7

With Formik and Yup, how do I display a form error for a field of a nested object?


I'm using React 18, Ionic 7, Formik 2.4 and Yup 0.32. I have an object with a nested object

export type Person = {
  name: string
  address: Address
}

The address objecdt is defined as

export type Address = {
  city: string
}

export default Address

My Yup schemas are defined similarly

export const personValidationSchema = Yup.object({
  name: Yup.string().required('First Name is required'),
  address: addressValidationSchema
})

The address schema is defined as

export const addressValidationSchema = Yup.object({
  city: Yup.string().required('City is required')
})

I'm not sure how to set up my error display logic to display an error if someone doesn't fill in a city in their Formik form. I have this

        <IonInput
            aria-label='City'
            placeholder='City'
            value={formikProps.values.address.city}
            onIonChange={(e) => formikProps.setFieldValue('address.city', e.detail.value)}
            name='address.city'
          />
          {formikProps.touched.address.city && formikProps.errors.address.city ? (
            <IonNote slot='error'>{formikProps.errors.address.city}</IonNote>
          ) : null}

The error if no name is entered displays just fine ...

          <IonInput
            aria-label='Name'
            placeholder='Name'
            value={formikProps.values.name}
            onIonChange={(e) => formikProps.setFieldValue('name', e.detail.value)}
            name='name'
          />
          {formikProps.touched.name && formikProps.errors.name ? (
            <IonNote slot='error'>{formikProps.errors.name}</IonNote>
          ) : null}

What else do I need to do? The example of this not working can be found here -- https://stackblitz.com/edit/an5yjh-v3s4ke?file=src%2Fschemas%2Faddress-schema.ts,src%2Fcomponents%2FMyForm.tsx,src%2Fschemas%2Fperson-schema.ts,src%2FApp.tsx


Solution

  • I took a look at the stackblitz example you posted and the bug I found was in MyForm component. initialFormValues had a typo in the spelling of city

     const initialFormValues: Person = {
        name: '',
        address: {
         ity: '', // this is the typo
        },
      };
    
    

    this would be the correct fix

     const initialFormValues: Person = {
        name: '',
        address: {
         city: '', // this is the fix
        },
      };
    
    

    Here's the complete component fix

    import React, { useEffect, useRef, useState } from 'react';
    import {
      IonList,
      IonItem,
      IonLabel,
      IonCheckbox,
      IonIcon,
      IonPage,
      IonContent,
      IonNote,
      IonButton,
      IonInput,
    } from '@ionic/react';
    import { pencilOutline } from 'ionicons/icons';
    import Person from './types/person.type';
    import { personValidationSchema } from '../schemas/person-schema';
    import { Formik, FormikProps } from 'formik';
    
    const MyForm: React.FC = () => {
      const formikRef = useRef<FormikProps<Person>>(null);
      const initialFormValues: Person = {
        name: '',
        address: {
          city: '',
        },
      };
    
      return (
        <Formik
          enableReinitialize={true}
          innerRef={formikRef}
          initialValues={initialFormValues}
          validationSchema={personValidationSchema}
          onSubmit={(values, { resetForm }) => {
            alert('Saved!');
          }}
        >
          {(formikProps) => (
            <form onSubmit={formikProps.handleSubmit}>
              <IonInput
                aria-label="Name"
                placeholder="Name"
                value={formikProps.values.name}
                onIonChange={(e) =>
                  formikProps.setFieldValue('name', e.detail.value)
                }
                name="name"
              />
              {formikProps.touched.name && formikProps.errors.name ? (
                <IonNote slot="error">{formikProps.errors.name}</IonNote>
              ) : null}
    
              <IonInput
                aria-label="City"
                placeholder="City"
                value={formikProps.values.address.city}
                onIonChange={(e) =>
                  formikProps.setFieldValue('address.city', e.detail.value)
                }
                name="address.city"
              />
              {formikProps.touched.address?.city &&
              formikProps.errors.address?.city ? (
                <IonNote slot="error">{formikProps.errors.address.city}</IonNote>
              ) : null}
    
              <IonButton type="submit">Save</IonButton>
            </form>
          )}
        </Formik>
      );
    };
    
    export default MyForm;