Formik: Custom field component loses focus when typing a single character

Created on 7 Sep 2018  路  12Comments  路  Source: formium/formik

Current Behavior

Hi, thanks for a really amazing library! :tada:

I'm trying to use a custom component that is a styled-components component. It sets a background when there are errors for that field. I use a prop called hasError to set the colour. However, React will throw a warning about non-DOM props if I don't remove the prop before passing it down to the actual component because styled-components passes on all props to its target component. My example code uses one implementation that uses style instead, but I want this component to be reusable.

Steps to Reproduce

Look at this CodeSandbox example.

There are 2 input fields: email address and password. The email address field uses a wrapper component (FormInputWithError) to take off the offending hasError prop and returns an instance of a styled-component that wraps a Formik Field.
The password field uses a styled-component that wraps a Formik Field directly (FormInput). It uses the style prop to change the background colour.

There are some validation rules:

  1. Email address must be present and a valid email address
  2. Password must be present

Follow these steps:

  1. Type nothing into the fields and click "Sign in"
  2. The field background will turn red and the error message will show
  3. Try to type something into the Email address field. You should see that the input loses focus after typing a character
  4. Type something into the password field. It does not have the problem.

Expected behavior

I expect to be able to keep typing into the field, without it losing focus.


  • Formik Version: 1.2.0
  • React Version: 16.4.2
  • TypeScript Version: 3.0.3
  • Browser and Version: Chrome 69.0.3497.81 and Firefox 63.0b3
  • OS: macOS High Sierra
  • Node Version: 8.11.3
  • Package Manager and Version: Yarn 1.9.4

Most helpful comment

This issue comes from the fact that you are creating the Input component inside your FormInputWithError, and React probably looses track of this component when re-rendering hence losing focus.

By doing the following, it works:

const Input = styled(FormikField)`
    background: ${({ hasError }) => (hasError ? red100 : shades100)};
    border: none;
    border-radius: 0.25rem;
    height: 2.5rem;
    padding-left: 0.5rem;
    width: 100%;
`;

const FormInputWithError = (props: any) => {
  return <Input {...props} />;
};

modified example: https://codesandbox.io/s/31m3z1pjyp

All 12 comments

This issue comes from the fact that you are creating the Input component inside your FormInputWithError, and React probably looses track of this component when re-rendering hence losing focus.

By doing the following, it works:

const Input = styled(FormikField)`
    background: ${({ hasError }) => (hasError ? red100 : shades100)};
    border: none;
    border-radius: 0.25rem;
    height: 2.5rem;
    padding-left: 0.5rem;
    width: 100%;
`;

const FormInputWithError = (props: any) => {
  return <Input {...props} />;
};

modified example: https://codesandbox.io/s/31m3z1pjyp

@boertel is correct. You are creating a new component in every single render in your example

Thanks both!

I ran across this same issue. When I wrote the form as a child of <Formik> it caused this issue to appear. When I rendered the form as part of the render prop, it didn't show this issue.

// input losing focus issue:
<Formik>
 {() => (<Form>...</Form>)}
</Formik>

// input worked as expected
const renderForm = () => (<Form>...</Form>);
<Formik render={renderForm} />

Hi guys,

I'm having the same problem and I don't know how to handle it.

I'll try to describe my situation as clear as possible.

So in our project we are using a component that is coming from our storybook. The component in storybook accepts either a string or a JSX Element

This is my component in storybook

import * as React from 'react'

import { IContentCardBuilderProps } from '.'
import TextPlaceholder from '../textplaceholder/TextPlaceholder'
import * as shortId from 'shortid'

import '../../assets/css/variables.css'
import * as s from './ContentCardBuilder.css'

class ContentCardBuilder extends React.PureComponent<IContentCardBuilderProps> {
  renderDescriptionField = (desc: string | JSX.Element = '') => {
    if (typeof desc === 'string') {
      return <TextPlaceholder>{desc}</TextPlaceholder>
    } else {
      return <React.Fragment>{desc}</React.Fragment>
    }
  }

  renderStringItems = (label: string, desc?: string | JSX.Element): JSX.Element => {
    return (
      <div className={s.items} key={shortId()}>
        <div className={s.titles}>
          <TextPlaceholder>{label}</TextPlaceholder>
        </div>
        <div className={s.description}>{this.renderDescriptionField(desc)}</div>
      </div>
    )
  }

  public render(): JSX.Element {
    const { fields } = this.props

    console.warn('RENDERING FIELD', fields)
    return (
      <div data-qa="ContentCardBuilder">
        {fields && fields.map(field => this.renderStringItems(field.label, field.value))}
      </div>
    )
  }
}

export default ContentCardBuilder

In our app, we have a component that imports ContentCardBuilder, here's the code:

import * as React from 'react'
import { Field, FormikProps } from 'formik'
import {
  ContentCardBuilder,
  ContentCardGenericWithLabelsWithEdit,
  ContentType,
  ELogoItemSource,
  IContentCardBuilderField,
} from '@qmiui/contentui'

import i18n from 'translations/i18n'
import { Container, ContainerForm, toEChipStatus } from '@models'
import { toETypeContent } from '@services/TypeContentConverter'
import { type } from 'os'
import { connect } from 'react-redux'
import { IApiFlowManager, IStoreState } from '~types/common'
import TitleField from '@modules/containerdetailedit/fields/TitleField'
import TypeField from '@modules/containerdetailedit/fields/TypeField'
import NumberOfSlot from '@modules/containerdetailedit/fields/NumberOfSlotField'
import { withApiFlow } from '@modules/apiflowmanager/ApiFlowManager'

interface IContainerDetailTabOwnProps extends IApiFlowManager {
  container: Container
  formProps: FormikProps<ContainerForm>
}

interface IStateToProps {
  filters: string[]
}

type TContainerDetailEdit = IStateToProps & IApiFlowManager

const ContainerDetailEdit: React.FC<TContainerDetailEdit & IContainerDetailTabOwnProps> = props => {
  const { container, filters, formProps } = props

  const buildDetailFields = (cnt: Container): IContentCardBuilderField[] => [
    { label: i18n.t('containerDetail:containerCardGeneric.label.id'), value: cnt.id },
    { label: i18n.t('containerDetail:containerCardGeneric.label.directLink'), value: cnt.slug },
    {
      label: i18n.t('containerDetail:containerCardGeneric.label.slotNumber'),
      value: <Field name="numberOfSlot" component={NumberOfSlot} />,
    },
  ]

  const buildSelectOptions = React.useCallback(
    (containersFilters: string[]) =>
      containersFilters.map((filter: string) => ({
        value: filter,
        optionContent: (
          <ContentType
            key={filter}
            type={container ? toETypeContent(filter) : undefined}
            placeholder={i18n.t([`eContainerType.${filter}`, filter ? filter : ''])}
          />
        ),
      })),
    [filters]
  )

  return (
    <React.Fragment>
      <ContentCardGenericWithLabelsWithEdit
        titleLabel={i18n.t('containerDetail:containerCardGeneric.label.title')}
        title={container ? container.name : ''}
        statusLabel={i18n.t('containerDetail:containerCardGeneric.label.status')}
        statusText={
          container
            ? i18n.t([`contentDetail:status.${container.status}`, container.status ? container.status : ''])
            : ''
        }
        type={container ? toETypeContent(container.type) : undefined}
        statusType={container ? toEChipStatus(container.status) : undefined}
        typeField={
          <TypeField
            selectOptions={buildSelectOptions(filters)}
            selectDefaultValue={container.type}
            fieldId="type"
            formProps={formProps}
          />
        }
        titleField={<Field name="name" component={TitleField} />}
        typePlaceholder={
          container ? i18n.t([`eContainerType.${container.type}`, container.type ? container.type : '']) : ''
        }
        typeLabel={i18n.t('containerDetail:containerCardGeneric.label.type')}
        source={ELogoItemSource.NA}
        sourceLabel={i18n.t('containerDetail:containerCardGeneric.label.source')}
        modificationDateLabel={i18n.t('containerDetail:containerCardGeneric.label.modificationDate')}
        ingestionDate={container && container.creationDate ? Date.parse(container.creationDate) : undefined}
        ingestionDateFormat={container ? i18n.t('date.longFormat') : ''}
        ingestionDateLocale={i18n.t('date.shortLanguage')}
        systemOrigin={'QUB'}
        originLabel={container ? i18n.t('containerDetail:containerCardGeneric.label.origin') : ''}
      />
      <ContentCardBuilder fields={buildDetailFields(container)} />
    </React.Fragment>
  )
}

const mapStateToProps = (state: IStoreState): IStateToProps => ({
  filters: state.contents.filters.data.container.type,
})

export default React.memo(connect(mapStateToProps)(withApiFlow(ContainerDetailEdit)))

As you can see, in function buildDetailFields we have value: <Field name="numberOfSlot" component={NumberOfSlot} />

This is my NumberOfSlot component

import * as React from 'react'
import { FieldProps } from 'formik'
import { TextField } from '@material-ui/core'
import { ContainerForm } from '@models'

const NumberOfSlot = ({ field }: FieldProps<ContainerForm>) => (
  <div>
    <TextField type="number" {...field} inputProps={{ min: '0', step: '1' }} />
  </div>
)

export default React.memo(NumberOfSlot)

I don't know how I can avoid re-rendering of either <NumberOfSlot /> or <ContentCardBuilder /> or if the problem is even there.

Also, I'd like to emphasize that I have another <Field /> component in <ContainerDetailEdit />, here: titleField={<Field name="name" component={TitleField} />}, here's the code:

import * as React from 'react'
import { FieldProps } from 'formik'
import { TextField } from '@material-ui/core'
import { ContainerForm } from '@models'

const TitleField = ({ field }: FieldProps<ContainerForm>) => (
  <div>
    <TextField type="text" {...field} />
  </div>
)

export default React.memo(TitleField)

Here's the component ContentCardGenericWithLabelsWithEdit in Storybook where I display the component (

<div className={s.row}>
          <div className={s.titles}>
            <TextPlaceholder>{typeLabel}</TextPlaceholder>
          </div>
          {typeField}
        </div>

)

import * as React from 'react'
import classNames from 'classnames'

import { IContentCardGenericWithLabelsWithEditProps } from '.'
import ChipStatus from '../chipstatus/ChipStatus'
import TextPlaceholder from '../textplaceholder/TextPlaceholder'
import Timestamp from '../timestamps/Timestamp'
import LogoItem from '../logoitem/LogoItem'

import '../../assets/css/variables.css'
import * as s from './ContentCardGenericWithLabelsWithEdit.css'

class ContentCardGenericWithLabelsWithEdit extends React.PureComponent<IContentCardGenericWithLabelsWithEditProps> {
  public render(): JSX.Element {
    const {
      statusText,
      statusType,
      source,
      systemOrigin,
      ingestionDate,
      ingestionDateLocale,
      ingestionDateFormat,
      typeLabel,
      titleLabel,
      sourceLabel,
      originLabel,
      modificationDateLabel,
      statusLabel,
      typeField,
      titleField,
    } = this.props

    return (
      <div data-qa="ContentCardGenericWithLabels">
        <div className={classNames(s.row, s.metadata)}>
          <div className={s.items}>
            <div className={s.titles}>
              <TextPlaceholder>{modificationDateLabel}</TextPlaceholder>
            </div>
            {ingestionDate && (
              <Timestamp format={ingestionDateFormat} locale={ingestionDateLocale}>
                {ingestionDate}
              </Timestamp>
            )}
          </div>
          <div className={s.items}>
            <div className={s.titles}>
              <TextPlaceholder>{statusLabel}</TextPlaceholder>
            </div>
            <ChipStatus status={statusType} placeholder={statusText} />
          </div>
        </div>
        <div className={s.row}>
          <div className={s.titles}>
            <TextPlaceholder>{typeLabel}</TextPlaceholder>
          </div>
          {typeField}
        </div>
        <div className={s.row}>
          <div className={s.titles}>
            <TextPlaceholder>{titleLabel}</TextPlaceholder>
          </div>
          <div>{titleField}</div>
        </div>
        <div className={s.row}>
          <div className={s.items}>
            <div className={s.titles}>
              <TextPlaceholder>{sourceLabel}</TextPlaceholder>
            </div>
            {source && <LogoItem src={source} width={50} />}
          </div>
          <div className={classNames(s.items, s.secondary)}>
            <div className={s.titles}>
              <TextPlaceholder>{originLabel}</TextPlaceholder>
            </div>
            <div>
              <TextPlaceholder>{systemOrigin}</TextPlaceholder>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

export default ContentCardGenericWithLabelsWithEdit

And for this one, the field does not loses focus. So I'm a bit confused because in one case, the field does not loses but in the other case it does loses focus.

Any guidance would be appreciated.

Thank you

@alveshelio in my case problem was that I was putting styled componen inside class-based or functional component. Meaning that formik's component was always rerendered because it thought that something was changed. So best thing is to define styled component outside component. If it is dynamic, use styled.div.attrs() for hihgly dynamic parts.

Not sure though if this is your case. Hope it helps.

Maybe in your case you can put custom component as const outside class and then pass it to the Formik field.

Hi @bologer,

Thank you for your reply.
Unfortunately, my problem is not the same as yours. I do not have a styled component I'm using a functional component and it is wrapped in React.memo() I've already tried many approaches, I've tried to declare the component in the render prop of <Field /> and it's always the same result, lost of focus.

@alveshelio there is a lot happening here. I'd recommend you distill your code down into the simplest parts that replicate your issue in a code sandbox. In doing this, you may realize what the issue is on your own. You're probably doing something like () => createMyField(), and every time you run the function you're creating a _new_ react component. Once you've distilled the code down, if you still haven't figured it out, please open a _new issue_ because this one is closed.

Edit: Also the sandbox for creating new issues is here -- you can access them via "New Issue -> Bug Report"

<Formik />: https://codesandbox.io/s/91q6rxmmrp
withFormik: https://codesandbox.io/s/437wy20rx4

Hi @johnrom,

Thank you, I've tried to reproduce the issues but I'm unable to. I'll try to tweak my code

@johnrom u fixed my issue thanks so much
had in fact () => createMyField()

@boertel Your first response fixed the issue for me as well. Shouldn't adding a key prop to the element also work as it helps React to identify the re-rendered element?

@aheidelberg sadly, it doesn't work.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jaredpalmer picture jaredpalmer  路  3Comments

green-pickle picture green-pickle  路  3Comments

giulioambrogi picture giulioambrogi  路  3Comments

Jucesr picture Jucesr  路  3Comments

najisawas picture najisawas  路  3Comments