Formik: How to use formik Field with reactstrap Input?

Created on 1 Jun 2018  路  18Comments  路  Source: formium/formik

I'm using reactstrap and its form controls.
I am able to use formik with reactstrap's Input component like so:

  <Input type='text' name="display_name"
     value={values.display_name}
     onChange={handleChange}
     onBlur={handleBlur} /> 

I am intrigued by the short-hand Field provides. For instance, this works for me:

     <Field name="display_name" type="text" />

however, it is rendered via a <input> component.

How do I render Fields via Input? The documentation on Field is sparse on that, mentioning a component and a render property. I tried:

    <Field name="display_name" component={Input} />

This renders an Input element, but formik doesn't pass the value and handleChange properties to it.
I reverse engineered the render method and found that it is passed a field and form property.
I tried:

    <Field name="display_name" render={({field}) => <Input {...field} /> }   />

which seems to work.

This makes me concerned that I don't know how to use formik in the intended way. I've checked the source code and formik passes a field/form bag object rather than the (flattened) properties of field to the rendered component.

Could you clarify the intended use/where I'm going wrong?

Most helpful comment

I've got this working via the tag property:

<Input
  tag={Field}
  name='name'
  type='text'
  component='input'
/>

All 18 comments

I'm seeing the same thing trying to use Field with my custom Input component.
Following the source code (https://github.com/jaredpalmer/formik/blob/master/src/Field.tsx#L194) components get passed the formik bag, which mean they receive a prop field which contains the handlers (onChange, onBlur, etc).

In my opinion this is counter-intuitive as I would now have to adapt my own generic component an re-wire the handlers from field.

I would have expected Field to spread ...field directly on the component.

Hola! So here's the deal, between open source and my day job and life and what not, I have a lot to manage, so I use a GitHub bot to automate a few things here and there. This particular GitHub bot is going to mark this as stale because it has not had recent activity for a while. It will be closed if no further activity occurs in a few days. Do not take this personally--seriously--this is a completely automated action. If this is a mistake, just make a comment, DM me, send a carrier pidgeon, or a smoke signal.

I too would have expected Field to spread ...field directly in the component, like @artisologic

This will probably be tackled in V2.

I've got this working via the tag property:

<Input
  tag={Field}
  name='name'
  type='text'
  component='input'
/>

I have created an npm module for this. You can try it out here - https://www.npmjs.com/package/reactstrap-formik

Thankyou @danielholmes

This code worked fine for me!

<Formik
    initialValues={{
        firstName: '',
    }}
    validationSchema={Yup.object().shape({
        firstName: Yup.string()
            .min(2, 'Too Short!')
            .max(5, 'Too Long!')
            .required('Required'),
    })}
    onSubmit={values => {
        // same shape as initial values
        console.log(values);
    }}>
    {({ errors, touched }) => (
        <Form>
            <FormGroup row>
                <Col md="2">
                    <Label for="firstName">First Name</Label>
                    <Input
                        type="text"
                        name="firstName"
                        tag={Field}
                        invalid={errors.firstName && touched.firstName}
                    />
                    <FormFeedback tooltip>{errors.firstName}</FormFeedback>
                </Col>
            </FormGroup>
            <Button color="primary">Submit</Button>
        </Form>
    )}
</Formik>

Thanks @danielholmes!

@Salles-FA mind sharing your imports too?

I managed to make it working by wrapping it in a custom component and passing the component as the component prop to the Formik Field, as explained in the documentation.

import React from 'react';
import {
  Input,
  FormFeedback
} from 'reactstrap';
import {Field as FormikField} from 'formik';

/**
 * A custom bootstrap input component to use with a formik field
 * @param field
 * @param touched
 * @param errors
 * @param props
 * @returns {*}
 * @constructor
 */
const CustomBootstrapInputComponent = ({
   field,  // { name, value, onChange, onBlur }
   form: {touched, errors}, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
   ...props
}) => (
    <div>
      <Input
          invalid={!!(touched[field.name] && errors[field.name])}
          {...field}
          {...props} />
      {touched[field.name] && errors[field.name] && <FormFeedback>{errors[field.name]}</FormFeedback>}
    </div>
);

/**
 * A Formik Field whose component is a custom bootstrap input
 * Usage: pass the same props to this component as you would pass to the Input field from reactstrap.
 * Example:
 *    <FormikFieldWithBootstrapInput
         name="email"
         placeholder='Email'
         bsSize='lg'
         className={'mb-2'}
         type={'email'} />

      //is equivalent with
      <FormikField name='email'>
         {({ field, form }) => (
          <Input
               placeholder='Email'
               bsSize='lg'
               className={'mb-2'}
               type={'email'}
               invalid={form.touched[field.name] && form.errors[field.name]}
                />
          )}
      </FormikField>
 * @param props
 * @returns {*}
 */
export default props => (
    <FormikField
        {...props}
        component={CustomBootstrapInputComponent}/>
);

And this is how I use it:

import FormikFieldWithBootstrapInput from './components/FormikFieldWithBootstrapInput';
const LoginForm = (props) => (
    <Formik
        initialValues={{
          email: '',
          password: ''
        }}
        validationSchema={LoginSchema}
        onSubmit={values => props.login(values.email, values.password)}
    >
      {() => (
          <Form>
            <FormikFieldWithBootstrapInput
                name="email"
                placeholder={props.t('email')}
                bsSize='lg'
                className={'mb-2'}
                autoComplete={'email'}
                type={'email'}/>
            <FormikFieldWithBootstrapInput
                name="password"
                placeholder={props.t('password')}
                bsSize='lg'
                className={'mb-2'}
                autoComplete={'current-password'}
                type={'password'}/>
            <p className={'text text-center text-danger'}>{props.authenticationError}</p>
            <Button color={'primary'} size='lg' type={'submit'} block>{props.t('login')}</Button>
          </Form>
      )}
    </Formik>
);

You can just replace the Input from CustomBootstrapInputComponent with your own input and it should work fine. Just make sure that you set the name prop on the created formik field, otherwise it will not work.

Just to add on to what @danielholmes and @Salles-FA included:

The important thing to remember is that you are still using Formik for your field control, while you're only effectively using the Bootstrap CSS for the form elements. Therefore, if you're converting from Formik to Formik + Reactstrap, it is imperative that you continue to import Form from formik and NOT reactstrap.

If you're using select boxes, you'll still need to include the component="select" that you did when doing a select box with Formik, otherwise you'll get an error.
For the custom components, you'll probably want to revert to what you had before. Just make sure you peer into the classes that get added on input for all your situations (especially validations), and hopefully your custom component can handle it.

Therefore, if you're converting from Formik to Formik + Reactstrap, it is imperative that you continue to import Form from formik and NOT reactstrap.

I think you can and probably should combine both. Reactstrap form has a tag= to that effect:

import { Form as FormikForm } from 'formik';
import { Form as RSForm } from 'reactstrap';

return <RSForm tag={FormikForm}> ... </RSForm>;

This way you should get the CSS from reactstrap and you get Formik's default submission handling.

Thoughts @wschmrdr ?

It certainly works. The only benefit I'm seeing, after looking at the documentation, to using Reactstrap's form is the shortcut for form-inline, but that could be done with className since you're likely already bringing in the CSS file via other components in reactstrap (e.g. Button, Row, Col), and in my personal opinion, I don't think it's worth the memory cost of an additional import to get that. However, you're absolutely correct that it's possible to use both, and some may prefer that way.

What if, in the future, formik or reactstrap change their implementation?

If we're going down that hyperbole, updating that line would be one step in a thousand that's going to need to happen on every form regardless of which way it gets implemented.

As was discussed previously (albeit for a different package; reactstrap and react-bootstrap are two different things), there is likely some mismatching between the Formik Form and the React-bootstrap Form that is causing weirdness. However, in this particular case, I would actually take the advice of @godmar and implement both forms using his method, as you'll likely be needing both of them. Then, when referencing Form for the purposes of Form.Check, make sure you're getting the react-bootstrap version. Give it a try and see if it works.

Interesting that there's another implementation... we'll see which one ends up being preferred after this goes to a final release version.

Thankyou @danielholmes

This code worked fine for me!

<Formik
  initialValues={{
      firstName: '',
  }}
  validationSchema={Yup.object().shape({
      firstName: Yup.string()
          .min(2, 'Too Short!')
          .max(5, 'Too Long!')
          .required('Required'),
  })}
  onSubmit={values => {
      // same shape as initial values
      console.log(values);
  }}>
  {({ errors, touched }) => (
      <Form>
          <FormGroup row>
              <Col md="2">
                  <Label for="firstName">First Name</Label>
                  <Input
                      type="text"
                      name="firstName"
                      tag={Field}
                      invalid={errors.firstName && touched.firstName}
                  />
                  <FormFeedback tooltip>{errors.firstName}</FormFeedback>
              </Col>
          </FormGroup>
          <Button color="primary">Submit</Button>
      </Form>
  )}
</Formik>

For input it works fine. thank you for posting this solution. But for textarea it render it as input.

For input it works fine. thank you for posting this solution. But for textarea it render it as input.

Did you try putting component and type attributes of "textarea" on the Input tag? That should get the thing to show as a textarea. And a rows attribute will give you the number of rows.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jaredpalmer picture jaredpalmer  路  3Comments

giulioambrogi picture giulioambrogi  路  3Comments

PeerHartmann picture PeerHartmann  路  3Comments

green-pickle picture green-pickle  路  3Comments

pmonty picture pmonty  路  3Comments