13

I am trying to create a React component to abstract away creating an Input group for my form. All inputs have the same layout - a Label, with the Input underneath and if errors/info text is present these are displayed under the Input.

Previously I was handling my own form state/handlers. Now I am experimenting with formik (with Yup validation) and have run into a problem dynamically accessing the error and touched fields when I have nested information.

Here is my input group component:

import React from 'react';
import { FormGroup, Label, Input, FormFeedback, FormText } from 'reactstrap';
import { Field, ErrorMessage } from 'formik';

const InputGroup = ({ name, label, type, info, required }) => {
  return (
    <FormGroup>
      <Label htmlFor={name}>{label}{required && '*'}</Label>
      <Field name={name}>
        {({field, form}) => (
          <Input {...field} id={name} type={
                 invalid={form.errors[name] && form.touched[name]} //problem here
          />
        )}
      </Field>
      {info && <FormText color="muted">{info}</FormText>}
      <ErrorMessage name={name}>
          {msg => <FormFeedback>{msg}</FormFeedback>}
      </ErrorMessage>
    </FormGroup>
  )
}

InputGroup.propTypes = {
  label: PropTypes.string,
  name: PropTypes.string.isRequired,
  type: PropTypes.string,
  info: PropTypes.string,
  required: PropTypes.bool
};

InputGroup.defaultProps = {
  type: 'text',
  required: false
};

As I am using bootstrap (reactstrap@7.x), the <FormFeedback> element requires the accompanying <Input> to be labelled with an invalid tag. In the above I dynamically assign invalid=true/false if the corresponding field on formik's form.errors object exists (ie. an error exists) and form.touched object is true (ie. the user has touched the input).

This works fine when formik is setup with a flat initialValues (eg. below), as the invalid={form.errors[name] && form.touched[name]} evaluates to (for eg.) invalid={form.errors[firstName] && form.touched[firstName]}

initialValues = {
  firstName: '',
  lastName: '',
  email: '',
  password: ''
}

However, when formik is setup with a nested initialValues (eg. below), the invalid={form.errors[name] && form.touched[name]} evaluates to invalid={form.errors[name.first] && form.touched[name.first]}. Ultimately, this will always evaluate to false, hence the input is always invalid=false, thusthe input is never marked with the error styling nor the error message displayed.

initialValues = {
  name: {
    first: '',
    last: ''
  },
  email: '',
  password: ''
}

How can I go about setting up my InputGroup component so that I can dynamically access the required fields on formik's error and touched objects regardless of whether it is flat or nested?

Braden
  • 680
  • 1
  • 11
  • 26

2 Answers2

29

Formik has a function getIn() that can extract a value from an object by a path (e.g. a path being something like name.first).

<Field name={name}>
  {({ field, form }) => (
    <Input
      {...field}
      id={name}
      invalid={getIn(form.errors, name) && getIn(form.touched, name)}
    />
  )}
</Field>

See an example here on CodeSandbox.

Ciarán Tobin
  • 7,306
  • 1
  • 29
  • 45
  • 3
    Excellent. I remembered Jared Palmer's React Alicante video on youtube and that formik supports lodash .get() with dot-syntax. Used lodash initially, but much better to have a built in method - one less package to work with. – Braden May 13 '19 at 09:18
  • dont forget to wrap the field name around backticks (``) like this- {getIn(form.errors, ` name `) && getIn(form.touched, ` name `)} – navinrangar Sep 23 '22 at 11:48
1

Formik also supports meta argument from the Field component, there is information specified for the exact field (value, touched, error).

const CustomFormikInput = (props) => {   
    return <Field name={props.name}>
       {({ field, meta }) => {
            console.log(JSON.stringify(meta)); // Log meta output
            const errorMsg = meta.touched ? meta.error : undefined;
            return <div>
                     <input {...field} {...props} />
                     {errorMsg}
                   </div>;
        }}
    </Field>;
}
Artur A
  • 7,115
  • 57
  • 60
  • but `meta.touched` does not work correctly with nested field, i.e it always return false when you click submit without touching the field (which is contrary to what state in the doc: https://formik.org/docs/guides/form-submission#pre-submit) – free2idol1 May 04 '23 at 07:54