reactjsreact-nativereact-final-formfinal-form

Unable for forwardRef in React Final Form


Using react-final-form i am unable to forwardRef to my TextInput for React Native. The ref is required for handling the Next button in keyboards and other forced-focus events in forms.

My setup is as follows - please note that some code has been removed for simplicity so this won't cut and paste.

// FormInputGroup/index.js

import React from 'react'
import PropTypes from 'prop-types'
import { Field } from 'react-final-form'

const FormInputGroup = ({ Component, validate, ...rest }) => (
  <Field component={Component} validate={validate} {...rest} />
)

FormInputGroup.propTypes = {
  name: PropTypes.string.isRequired,
  Component: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
  validate: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
}

export default FormInputGroup
// Form/index.js

import { Form } from 'react-final-form'
import { FormInputGroup, FormInputText, ButtonPrimary } from '/somewhere/'
...

const passwordInputRef = useRef()

<Form
  onSubmit={({ email, password }) => {
    // submit..
  }}
>
  {({ handleSubmit }) => (
    <>
      <FormInputGroup
        name="email"
        Component={FormInputText}
        returnKeyType="next"
        onSubmitEditing={() => passwordInputRef.current.focus()}
        blurOnSubmit={false}
      />
      <FormInputGroup
        name="password"
        Component={FormInputText}
        returnKeyType="go"
        onSubmitEditing={handleSubmit}
        ref={passwordInputRef} // <-- This does not work which i think is to be expected...
      />
      <ButtonPrimary loading={loading} onPress={handleSubmit}>
        Submit
      </ButtonPrimary>
    </>
  )}
</Form>

...
// FormInputText/index.js

const FormInputText = forwardRef( // <-- added forwardRef to wrap the component
  (
    {
      input,
      meta: { touched, error },
      label,
      ...
      ...rest
    },
    ref,
  ) => {
    return (
      <View style={styles.wrapper}>
        {label ? (
          <Text bold style={styles.label}>
            {label}
          </Text>
        ) : null}
        <View style={styles.inputWrapper}>
          <TextInput
            onChangeText={input.onChange}
            value={input.value}
            ...
            ref={ref}
            {...rest}
          />
        </View>
      </View>
    )
  },
)

I think the code falls down with the <FormInputGroup /> component. A clue to this is if i change the rendering on the form to be as follows;

...

     <FormInputGroup
        name="password"
        Component={props => <FormInputText {...props} ref={passwordInputRef} />} // <-- changed
        returnKeyType="go"
        onSubmitEditing={handleSubmit}
      />

...

This does seem to forward the ref, but "breaks" final-form with each keystroke dismissing the keyboard, presumably due to a re-render.


Solution

  • You didn't forward the ref from FormInputGroup.

    You need to capture the ref at the level it is passed down and further forward it to the children. This is how it should be:

    const FormInputGroup = forwardRef(({ Component, validate, ...rest }, ref) => (
      <Field ref={ref} component={Component} validate={validate} {...rest} />
    ))