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?
Advertisement
Answer
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.