React Hooks — ввод теряет фокус при динамическом добавлении или удалении полей ввода

У меня есть форма, отображаемая в модальном окне. Эта форма разделена на несколько вкладок. Один из них имеет два сгруппированных поля: выпадающий список стран и текстовое поле описания. Есть кнопка «Добавить», которая позволяет создать новое сгруппированное поле.

Проблема в том, что каждый раз, когда я заполнял текстовое поле, я терял фокус, потому что форма перерисовывалась. Я попытался переместить форму за пределы функции по умолчанию, но у меня все та же проблема.

Я также устанавливаю уникальные ключи для каждого элемента, но все же.

Я знаю, что есть много документации по этому поводу, но, несмотря на это, она не работает. Я мог бы настроить автофокус, но когда групповых полей больше одного, фокус будет идти на последний элемент.

Я использую Material UI (реакция 17)

Во всяком случае, ниже приведен код (который был усечен для лучшей наглядности):

  function GeoForm (props) {
            return(
            <React.Fragment>
                <Autocomplete
                  id = {"country"+props.i}
                  style = {{ width: 300 }}
                  options = {Object.keys(countries.getNames('fr')).map(e => ({code: e, label: countries.getNames('fr')[e]}))}
                  getOptionSelected = {(option, value) => (option.country === value.country)}
                  classes = {{
                    option: props.classes.option,
                  }}
                  
                  defaultValue = {props.x.country}
                  key = {"country"+props.i}
                  name = "country"
                  onChange = {(e,v) => props.handleInputGeoCountryChange(e, v, props.i)}
                  getOptionLabel = {(option) => (option ? option.label : "")}
                  renderOption = {(option) => (
                    <React.Fragment>
                      {option.label}
                    </React.Fragment>
                  )}
                  renderInput = {(params) => (
                    <TextField
                      {...params}
                      label = "Choose a country"
                      variant = "outlined"
                      inputProps = {{
                        ...params.inputProps,
                        autoComplete: 'new-password', // disable autocomplete and autofill
                      }}
                    />
                  )}
                />
              <TextField
                  id = {"destination"+props.i}
                  onChange = {e => props.handleInputGeoDestinationChange(e, props.i)}
                  defaultValue = {props.x.destination}
                  name = "destination"
                  key = {"destination"+props.i}
                  margin = "dense"
                  label = "Destination"
                  type = "text"
                />
                {props.inputGeoList.length !== 1 && <button
                className = "mr10"
                onClick = {() => props.handleRemoveGeoItem(props.i)}>Delete</button>}
                {props.inputGeoList.length - 1 === props.i && 
                  <Button
                  onClick = {props.handleAddGeoItem}
                  variant = "contained"
                  color = "primary"
                  //className = {classes.button}
                  endIcon = {<AddBoxIcon />}
                  >
                  Add
                  </Button>
                }
            </React.Fragment>
          )
        }
    
 export default function modalInfo(props) {
    const classes = useStyles();
      const [openEditDialog, setOpenEditDialog] = React.useState(false);
    
      const handleAddGeoItem = (e) => {
        console.info(e);
        setInputGeoList([...inputGeoList, { country: "", destination: "" }]);
      };
    
       // handle input change
      const handleInputGeoCountryChange = (e, v, index) => {
        const list = [...inputGeoList];
        list[index]['country'] = v;
        setInputGeoList(list);
      };
    
      const handleInputGeoDestinationChange = (e, index) => {
        const { name, value } = e.target;
        console.info(name);
        const list = [...inputGeoList];
        list[index][name] = value;
        setInputGeoList(list);
        console.info(inputGeoList)
      };
    
      // handle click event of the Remove button
      const handleRemoveGeoItem = index => {
        const list = [...inputGeoList];
        list.splice(index, 1);
        setInputGeoList(list);
      };
    
    const TabsEdit = (props) => {
    return(
          <div className = {classes.root}>
            <form className = {classes.form} noValidate onSubmit = {onSubmit}>
            <Tabs
              orientation = "vertical"
              variant = "scrollable"
              value = {value}
              onChange = {handleChangeEvent}
              aria-label = "Vertical tabs example"
              className = {classes.tabs}
            >
            [...]
              <Tab label = "Geo-targeting" {...a11yProps(4)} disableRipple />
            </Tabs>
            [...]
            </TabPanel>
            <TabPanel value = {value} index = {4}>
            { 
              inputGeoList.map((x, i)=>{
                return(
                  <GeoForm 
                    inputGeoList = {inputGeoList}
                    x = {x}
                    i = {i}
                    handleRemoveGeoItem = {handleRemoveGeoItem}
                    handleInputGeoDestinationChange = {handleInputGeoDestinationChange}
                    handleInputGeoCountryChange = {handleInputGeoCountryChange}
                    handleAddGeoItem = {handleAddGeoItem}
                    handleInputGeoDestinationChange = {handleInputGeoDestinationChange}
                    classes = {classes}
                  />
                )
              })
            }
            </TabPanel>
            <TabPanel value = {value} index = {5}>
              Mobile-targeting
            </TabPanel>
            <DialogActions>
              <Button onClick = {props.handleClose} color = "primary">
                Annuler
              </Button>
              <Button type = "submit" color = "primary">
                Enregistrer
              </Button>
          </DialogActions>
            </form>
          </div>
        )
      }
    
     return (
        <div>
          <div>
          <EditIconButton onClickEdit = {() => setOpenEditDialog(true)} />
          </div>
          <div>
            <EditDialog open = {openEditDialog} handleClose = {() => setOpenEditDialog(false)} >
              <TabsEdit/>
            </EditDialog>
          </div>
    
        
        </div>
      );
    

коды и ящик

Любая помощь или предложение приветствуются. Спасибо

Попробуйте иметь работающий фрагмент вашего кода в pastebin, например, используйте CodeSandBox, это пример с веб-страницы MUI: codeandbox.io/s/tuebl. Так легче ответить на ваш вопрос.

David I. Samudio 23.12.2020 04:07

Спасибо. Я обновил ссылку codeandbox

MrJibus 23.12.2020 12:21
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
1
2
1 185
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вкратце: ваш компонент TabsEdit был определен внутри другого компонента, поэтому React каждый раз перемонтировал его как новый компонент, в результате чего сфокусированное состояние терялось. Этот Pastebin исправляет ваш код, сохраняет фокус при вводе.

NL;PR: я страдал от этой же проблемы в течение нескольких месяцев, не только реквизиты проверяются на соответствие, но и ссылка на память компонента. Поскольку ссылка на функцию компонента каждый раз разная, React обрабатывает ее как новый компонент, тем самым размонтируя предыдущий компонент, что приводит к потере состояния, в вашем случае фокуса.

Другие вопросы по теме