Search code examples
reactjsreact-reduxreact-functional-component

Losing focus on input field after typing a character in React with redux-form library


I'm working in React with redux-form library. I would like to create simple form with 2 fields in functional component. After typing just 1 character in one of them I lose focus. I have created function renderInput which return Material UI TextField inputs. With native HTML input I can observe the same error. I tried to move renderInput to component inside but it haven't helped me.

Below I show my functional component implementation:

import React from "react";
import {Field, InjectedFormProps, reduxForm} from "redux-form";

import IStream from "./types/Istream";
import {CommonFieldProps, GenericField, WrappedFieldProps} from "redux-form/lib/Field";
import {TextField} from "@material-ui/core";
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';

const useStyles = makeStyles((theme: Theme) =>
    createStyles({
        root: {
            '& .MuiTextField-root': {
                margin: theme.spacing(1),
                width: '25ch',
            },
        },
    }),
);

export const streamMethods = () => {
    const renderInput = (formProps: WrappedFieldProps): JSX.Element => {
        return (
            <TextField
                required
                id="outlined-required"
                value={formProps.input.value}
                onChange={formProps.input.onChange}
            />
        );
    }
    return {
        renderInput,
    }
}

const StreamCreate = (props: InjectedFormProps<IStream>) => {
    const classes = useStyles();
    return (
        (
            <form className={classes.root}>
                <Field name="title" component={streamMethods().renderInput}/>
                <Field name="description" component={streamMethods().renderInput}/>
            </form>
        )
    )
}

export default reduxForm<IStream, {}>({
    form: "streamCreate"   // name of using form
})(StreamCreate);

Solution

  • In general, if you lose focus in a field, it means the component gets remounted. Components get remounted (among other things) when their key changes or when their identity (the actual function/class for the component) changes.

    You're returning a component function with a new identity for each render from streamMethods() (in fact, twice per render).

    I tried to move renderInput to component inside but it haven't helped me.

    That has the same issue; the renderInput identity also changes per render if it's an inner function. (If you really need to declare a component within another (functional) component, you could use the React.useMemo() hook to have it retain its identity.)

    Change things up to

    const StreamInput = (formProps: WrappedFieldProps): JSX.Element => {
      return (
        <TextField
          required
          id="outlined-required"
          value={formProps.input.value}
          onChange={formProps.input.onChange}
        />
      );
    };
    const StreamCreate = (props: InjectedFormProps<IStream>) => {
      const classes = useStyles();
      return (
        <form className={classes.root}>
          <Field name="title" component={StreamInput} />
          <Field name="description" component={StreamInput} />
        </form>
      );
    };
    

    and you should be golden.