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

112 Views Asked by At

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

2

There are 2 best solutions below

0
X8inez On BEST ANSWER

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;


0
Milind On

Make sure that to display errors using formik and yup, you are using formikProps.errors and its structure match the structure of the form values.

One more thing, your error path for city looks wrong.it should be address.city

Try using the below component -

<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 above change in the code that I have includes is that - i am using formikProps.touched.address?.city instead of formikProps.touched.address.city. Also the same goes with formikProps.errors.address?.city instead of formikProps.errors.address.city.

Plus, set your initial values -

const initialValues: Person = {
  name: '',
  address: {
    city: '',
  },
};

With the above changes, you should be able to display errors for the city field within the nested address object.

Let me know if that works.