MUI Custom Text Field loses focus on state change
Asked Answered
L

7

18

I'm using MUI library to create my React Js app.

Here I'm using the controlled Text Field component to create a simple search UI.

But there is something strange. The Text Field component loses focus after its value is changed. This is my first time facing this problem. I never faced this problem before.

How this could happen? And what is the solution.

Here is the code and the playground: https://codesandbox.io/s/mui-autocomplete-lost-focus-oenoo?

Note: if I remove the breakpoint type from the code, the Text Field component still loses focus after its value is changed.

Leech answered 30/11, 2021 at 4:27 Comment(0)
T
35

It's because you're defining a component inside another component, so that component definition is recreated every time the component renders (and your component renders every time the user types into the input).

Two solutions:

  1. Don't make it a separate component.

    Instead of:

    const MyComponent = () => {
      const MyInput = () => <div><input /></div>; // you get the idea
    
      return (
        <div>
          <MyInput />
        </div>
      );
    };
    

    Do:

    const MyComponent = () => {    
      return (
        <div>
          <div><input /></div> {/* you get the idea */}
        </div>
      );
    };
    
  2. Define the component outside its parent component:

    const MyInput = ({value, onChange}) => (
      <div>
        <input value={value} onChange={onChange} />
      </div>
    );
    
    const MyComponent = () => {
      const [value, setValue] = useState('');
    
      return (
        <div>
          <MyInput
            value={value}
            onChange={event => setValue(event.target.value)}
          />
        </div>
      );
    };
    
Telephony answered 30/11, 2021 at 5:39 Comment(2)
Or use useMemo() to wrap your component and use it somewhere else.Cienfuegos
Can I just say “Thank you so much for this”? It’s a life saver.Bordello
B
9

Be careful about your components' keys, if you set dynamic value as a key prop, it will also cause focus lost. Here is an example


{people.map(person => (
    <div key={person.name}>
      <Input type="text" value={person.name} onChange={// function which manipulates person name} />
    </div>
))}

Bouldon answered 10/12, 2022 at 12:43 Comment(1)
This is the correct answer when u have multiple items...Recidivism
W
8

For MUI V5

Moved your custom-styled code outside the component

For example:

import React from 'react';
import { useTheme, TextField, styled } from '@mui/material'; 
import { SearchOutlined } from '@mui/icons-material';

interface SearchInputProps {   placeholder?: string;   onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;   value: string; dataTest?: string; }

const StyledSearchInput = styled(TextField)(({ theme }: any) => {   return {
    '& .MuiOutlinedInput-root': {
      borderRadius: '0.625rem',
      fontSize: '1rem',
      '& fieldset': {
        borderColor: `${theme.palette.text.secondary}`
      },
      '&.Mui-focused fieldset': {
        borderColor: `${theme.palette.primary}`
      }
    }   }; });

const SearchInput: React.FC<SearchInputProps> = ({   placeholder = 'Search...',   onChange,   value,   dataTest,   ...props }) => {   const theme = useTheme();

  return (
    <StyledSearchInput
      {...props}
      onChange={onChange}
      placeholder={placeholder}
      variant="outlined"
      value={value}
      inputProps={{ 'data-testid': dataTest ? dataTest : 'search-input' }}
      InputProps={{
        startAdornment: (
          <SearchOutlined
            sx={{ color: theme.palette.text.secondary, height: '1.5rem', width: '1.5rem' }}
          />
        )
      }}
    />   ); };

export default SearchInput;
Waistline answered 30/11, 2022 at 10:50 Comment(0)
M
0

A bit late to the conversation. But adding to SchemeSonic's answer, if you have 'dynamic' component keys which you're passing to a component which renders, for arguments sake, a list of input fields, and you want to focus on a specific input field when it is mounted, you can use a ref to store a mapping of the input field ids to onMount callbacks.

Then, after mounting you can set a timeout to call the onMount callbacks. This way, you can focus on the component (or do anything using the input ref) when it is mounted.

Here's a snippet to the codesandbox solution.

<InputFieldList
      inputFieldIds={inputFieldIds}
      renderInputField={(id) => (
        <StyledTextField
          key={id}
          label={id}
          variant="outlined"
          size="small"
          inputRef={id === fieldToFocus ? fieldToFocusRef : undefined}
          value={values[id]}
          onChange={handleChange(id)}
        />
      )}
      onMount={(id) => {
        // Focus on the input field when it is mounted
        if (id === fieldToFocus) fieldToFocusRef.current?.focus();
      }}
    />
Mesothorax answered 28/7, 2023 at 9:36 Comment(0)
H
0

use focused propes

<TextField
   focused={field.value && true}
/>
Heaton answered 18/4 at 11:58 Comment(0)
A
0

Fix is very simple

return (
    <div>
      <CustomTextField ... />
      <>{`search value: "${search}"`}</>
    </div>
  );

https://codesandbox.io/p/devbox/mui-autocomplete-lost-focus-forked-w6ftvk?file=%2Fsrc%2FApp.jsx%3A56%2C7-70%2C41

just wrap the dynamic element with Fragments.

Altissimo answered 10/5 at 13:25 Comment(0)
H
0

For people still stuck around this, avoid wrapping your TextField in a custom styled div, as this causes all components in it to lose focus during re-rendering:

Don't do this:

const Actions = styled('div')(() => ({
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    width: '50vw',
    height: '10vh',
}));


....

const [search , setsearch] = React.useState('');


<Actions>
  <Input
    value={search}
    onChange={(value) => {
      setsearch(value);
    }}
    placeholder="Search Client"
  />
</Actions>

Instead use a div to layout your text field:

<div style = {{
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'flex-end',
    alignItems: 'center',
    width: '90vw',
    height: '10vh',
}}>
    <Input
        value={searchValue}
        onChange={(value) => {
            setSearchValue(value);
        }}
        placeholder="Search Client"
        
    />
</div>

I think this is a bug, but so far that is my workaround.

Halifax answered 9/6 at 13:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.