Eran Abir
Eran Abir

Reputation: 1097

React - Material UI - TextField controlled input with custom input component not working properly losing focus

I am trying to implement custom input element for TextField component from Material UI

example :


export const InputsPage: React.FC = () => {
  const [value, setValue] = useState('');
  return (
    <Paper>
      <Box p={2}>
        <TextField
          value={value}
          onChange={(e) => {
            setValue(e.target.value);
          }}
          color='primary'
          label='FROM'
          placeholder='Placeholder'
          InputProps={{
            inputComponent: ({ inputRef, ...rest }) => <input ref={inputRef} {...rest} type='text' />,
          }}
        />
      </Box>
    </Paper>
  );
};

because i am using controlled input with my own state the input is not working properly ... each time ill trying to type some thing the input will loss focus so i need to type each char/number and make a click on the input again to make a focus so i can continue typing

if ill use uncontrolled input it will work properly

here is an example what is happening : codeSandbox

Upvotes: 5

Views: 16765

Answers (2)

G&#246;khan Duman
G&#246;khan Duman

Reputation: 192

If you want to use masked input with material ui and form library you could do this:

import React, { memo } from "react";
// MASKED INPUT
import MaskedInput from "react-text-mask";
// MATERIAL UI
import { Phone } from "@mui/icons-material";
import { InputAdornment, TextField } from "@mui/material";
interface MaskedPhoneInputProps {
  fieldRef: (ref: HTMLElement | null) => void;
  field: any;
  errors: any;
}
const MaskedPhoneInput = ({
  fieldRef,
  field,
  errors,
}: MaskedPhoneInputProps) => {
  return (
    <MaskedInput
      {...field}
      ref={(ref) => {
        fieldRef(ref ? ref.inputElement : null);
      }}
      mask={[
        "0",
        "(",
        /[1-9]/,
        /\d/,
        /\d/,
        ")",
        " ",
        /\d/,
        /\d/,
        /\d/,
        "-",
        /\d/,
        /\d/,
        /\d/,
        /\d/,
      ]}
      guide={false}
      keepCharPositions
      render={(ref, props) => (
        <TextField
          inputRef={ref}
          {...props}
          variant="outlined"
          label="Telefon Numarası"
          fullWidth
          size="small"
          placeholder="Telefon numaranızı giriniz"
          error={!!errors?.phone}
          helperText={errors?.phone?.message}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start">
                <Phone sx={{ color: "text.disabled" }} />
              </InputAdornment>
            ),
          }}
        />
      )}
    />
  );
};

export default memo(MaskedPhoneInput);

Then you could use it in any controller (formik, react hook form) like this:

<Controller
              name="phone"
              control={control}
              rules={{ required: true }}
              render={({ field: { ref, ...rest } }) => (
                <MaskedPhoneInput fieldRef={ref} field={rest} errors={errors} />
              )}
            /> 

WARNING

ref={(ref) => {
        fieldRef(ref ? ref.inputElement : null);
      }}

Defining ref like it is important, if you don't define you may take error about focus like elm.focus is not a function in react hook form.

Upvotes: 0

Ryan Cogswell
Ryan Cogswell

Reputation: 80986

The problem is that you are defining inline the component type for the inputComponent prop. This means that with each re-render it will be considered by React to be a new component type, so instead of just re-rendering, the element will be remounted (removed completely from the DOM and re-added) which results in focus being lost.

You can fix this by defining the component type (CustomInputComponent in the example) at the top-level as shown in the example below:

import React, { useState } from "react";
import "./styles.css";
import { TextField } from "@material-ui/core";
const CustomInputComponent = ({ inputRef, ...rest }) => (
  <input ref={inputRef} {...rest} type="text" />
);
export default function App() {
  const [value, setValue] = useState("");
  return (
    <div className="App">
      <TextField
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
        }}
        color="primary"
        label="FROM"
        placeholder="Placeholder"
        InputProps={{
          inputComponent: CustomInputComponent
        }}
      />
    </div>
  );
}

Edit custom input component

Upvotes: 7

Related Questions