React.js - входной фокус при реинжиниринге

Я просто пишу для ввода текста и в onChange событии я вызываю setState, поэтому React изменяет мой пользовательский интерфейс. Проблема в том, что ввод текста всегда теряет фокус, поэтому мне нужно снова сконфигурировать его для каждой буквы: D.

var EditorContainer = React.createClass({

    componentDidMount: function () {
        $(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
    },

    componentDidUpdate: function () {
        console.log("zde");
        $(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
    },

    changeSelectedComponentName: function (e) {
        //this.props.editor.selectedComponent.name = $(e.target).val();
        this.props.editor.forceUpdate();
    },

    render: function () {

        var style = {
            height: this.props.height + 'px'
        };
        return (
            <div className="container" style={style}>
                <div className="row">
                    <div className="col-xs-6">
                    {this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
                    {this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
                    </div>
                    <div className="col-xs-6">
                        <ComponentTree editor={this.props.editor} components={this.props.components}/>
                    </div>
                </div>
            </div>
        );
    }

});

Ответ 1

Не видя остальной код, это предположение. Когда вы создаете EditorContainer, укажите уникальный ключ для компонента:

<EditorContainer key="editor1"/>

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

Ответ 2

Если это проблема в ответном маршрутизаторе <Route/>, используйте render prop вместо component.

<Route path="/user" render={() => <UserPage/>} />


Потеря фокуса происходит, потому что component prop использует React.createElement каждый раз вместо того, чтобы просто перерисовывать изменения.

Подробности здесь: https://reacttraining.com/react-router/web/api/Route/component

Ответ 3

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

Причина, по которой input теряли фокус в моем случае, была связана с тем, что я повторно отображал input при изменении состояния.

Код ошибки:

import React from 'react';
import styled from 'styled-components';

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    const Container = styled.div'';
    const Input = styled.input'';
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

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

Исправить несложно, выполнить модуляризацию с самого начала, другими словами: "Переместить компонент Input из функции render ".

Фиксированный код

import React from 'react';
import styled from 'styled-components';

const Container = styled.div'';
const Input = styled.input'';

class SuperAwesomeComp extends React.Component {
  state = {
    email: ''
  };
  updateEmail = e => {
    e.preventDefault();
    this.setState({ email: e.target.value });
  };
  render() {
    return (
      <Container>
        <Input
          type="text"
          placeholder="Gimme your email!"
          onChange={this.updateEmail}
          value={this.state.email}
        />
      </Container>
    )
  }
}

Ссылка к решению: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947

Ответ 4

Мой ответ похож на то, что сказал @z5h.

В моем случае я использовал Math.random() для генерации уникального key для компонента.

Я думал, что key используется только для запуска рендеринга для этого конкретного компонента, а не для рендеринга всех компонентов в этом массиве (я возвращаю массив компонентов в моем коде). Я не знал, что он используется для восстановления состояния после перерисовки.

Удаление этого сделало работу для меня.

PS: Я мог бы прокомментировать это, но у меня недостаточно репутации для этого.

Ответ 5

Я просто столкнулся с этой проблемой и пришел сюда за помощью. Проверьте свой CSS! Поле ввода не может иметь user-select: none; или оно не будет работать на iPad.

Ответ 6

У меня такое же поведение.

Проблема в моем коде заключалась в том, что я создал вложенный массив элементов jsx, например:

const example = [
            [
                <input value={'Test 1'}/>,
                <div>Test 2</div>,
                <div>Test 3</div>,
            ]
        ]

...

render = () => {
    return <div>{ example }</div>
}

Каждый элемент этого вложенного массива перерисовывается каждый раз, когда я обновляю родительский элемент. И поэтому входы теряют там "ref" опору каждый раз

Я исправил проблему с преобразованием внутреннего массива в реагирующий компонент (функция с функцией рендеринга)

const example = [
            <myComponentArray />
        ]

 ...

render = () => {
    return <div>{ example }</div>
}

РЕДАКТИРОВАТЬ:

Та же проблема возникает, когда я создаю вложенный React.Fragment

const SomeComponent = (props) => (
    <React.Fragment>
        <label ... />
        <input ... />
    </React.Fragment>
);

const ParentComponent = (props) => (
    <React.Fragment>
        <SomeComponent ... />
        <div />
    </React.Fragment>
);

Ответ 7

Применение атрибута autoFocus к элементу input может выполняться в качестве обходного пути в ситуациях, когда требуется только один ввод, который необходимо сфокусировать. В этом случае атрибут key был бы лишним, потому что это всего лишь один элемент, и вам больше не придется беспокоиться о том, чтобы разбивать элемент input на свой собственный компонент, чтобы избежать потери внимания при повторном рендеринге основного компонента.

Ответ 8

Основная причина заключается в следующем: при повторном рендеринге React ваш предыдущий DOM-код будет недействительным. Это означает, что реакция изменила дерево DOM, и вы this.refs.input.focus не будут работать, потому что вход здесь больше не существует.

Ответ 9

Ответы не помогли мне, вот что я сделал, но у меня была уникальная ситуация.

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

render(){
   const MyInput = () => {
      return <input onChange={(e)=>this.setState({text: e.target.value}) />
   }
   return(
      <div>
         <MyInput />
      </div>
   )

Но это заставило его потерять фокус, когда я поместил код непосредственно в div, с которым он работал.

return(
   <div>
      <input onChange={(e)=>this.setState({text: e.target.value}) />
   </div>
)

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

Ответ 10

У меня была такая же проблема с HTML-таблицей, в которой у меня есть строки ввода текста в столбце. внутри цикла я читаю объект json и создаю строки, в частности, у меня есть столбец с inputtext.

http://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/

Мне удалось решить это следующим образом

import { InputTextComponent } from './InputTextComponent';
//import my  inputTextComponent 
...

var trElementList = (function (list, tableComponent) {

    var trList = [],
        trElement = undefined,
        trElementCreator = trElementCreator,
        employeeElement = undefined;



    // iterating through employee list and
    // creating row for each employee
    for (var x = 0; x < list.length; x++) {

        employeeElement = list[x];

        var trNomeImpatto = React.createElement('tr', null, <td rowSpan="4"><strong>{employeeElement['NomeTipologiaImpatto'].toUpperCase()}</strong></td>);
        trList.push(trNomeImpatto);

        trList.push(trElementCreator(employeeElement, 0, x));
        trList.push(trElementCreator(employeeElement, 1, x));
        trList.push(trElementCreator(employeeElement, 2, x));

    } // end of for  

    return trList; // returns row list

    function trElementCreator(obj, field, index) {
        var tdList = [],
            tdElement = undefined;

        //my input text
        var inputTextarea = <InputTextComponent
            idImpatto={obj['TipologiaImpattoId']}//index
            value={obj[columns[field].nota]}//initial value of the input I read from my json data source
            noteType={columns[field].nota}
            impattiComposite={tableComponent.state.impattiComposite}
            //updateImpactCompositeNote={tableComponent.updateImpactCompositeNote}
        />

        tdElement = React.createElement('td', { style: null }, inputTextarea);
        tdList.push(tdElement);


        var trComponent = createClass({

            render: function () {
                return React.createElement('tr', null, tdList);
            }
        });
        return React.createElement(trComponent);
    } // end of trElementCreator

});
...    
    //my tableComponent
    var tableComponent = createClass({
        // initial component states will be here
        // initialize values
        getInitialState: function () {
            return {
                impattiComposite: [],
                serviceId: window.sessionStorage.getItem('serviceId'),
                serviceName: window.sessionStorage.getItem('serviceName'),
                form_data: [],
                successCreation: null,
            };
        },

        //read a json data soure of the web api url
        componentDidMount: function () {
            this.serverRequest =
                $.ajax({
                    url: Url,
                    type: 'GET',
                    contentType: 'application/json',
                    data: JSON.stringify({ id: this.state.serviceId }),
                    cache: false,
                    success: function (response) {
                        this.setState({ impattiComposite: response.data });
                    }.bind(this),

                    error: function (xhr, resp, text) {
                        // show error to console
                        console.error('Error', xhr, resp, text)
                        alert(xhr, resp, text);
                    }
                });
        },

        render: function () {
    ...
    React.createElement('table', {style:null}, React.createElement('tbody', null,trElementList(this.state.impattiComposite, this),))
    ...
    }



            //my input text
            var inputTextarea = <InputTextComponent
                        idImpatto={obj['TipologiaImpattoId']}//index
                        value={obj[columns[field].nota]}//initial value of the input I read //from my json data source
                        noteType={columns[field].nota}
                        impattiComposite={tableComponent.state.impattiComposite}//impattiComposite  = my json data source

                    />//end my input text

                    tdElement = React.createElement('td', { style: null }, inputTextarea);
                    tdList.push(tdElement);//add a component

        //./InputTextComponent.js
        import React from 'react';

        export class InputTextComponent extends React.Component {
          constructor(props) {
            super(props);
            this.state = {
              idImpatto: props.idImpatto,
              value: props.value,
              noteType: props.noteType,
              _impattiComposite: props.impattiComposite,
            };
            this.updateNote = this.updateNote.bind(this);
          }

        //Update a inpute text with new value insert of the user

          updateNote(event) {
            this.setState({ value: event.target.value });//update a state of the local componet inputText
            var impattiComposite = this.state._impattiComposite;
            var index = this.state.idImpatto - 1;
            var impatto = impattiComposite[index];
            impatto[this.state.noteType] = event.target.value;
            this.setState({ _impattiComposite: impattiComposite });//update of the state of the father component (tableComponet)

          }

          render() {
            return (
              <input
                className="Form-input"
                type='text'
                value={this.state.value}
                onChange={this.updateNote}>
              </input>
            );
          }
        }

Ответ 11

Для меня это было вызвано тем, что поле ввода поиска отображалось в том же компоненте (называемом UserList), что и список результатов поиска. Таким образом, всякий раз, когда результаты поиска менялись, весь компонент UserList перерисовывался, включая поле ввода.

Моим решением было создать совершенно новый компонент с именем UserListSearch, который отделен от UserList. Мне не нужно было устанавливать ключи в полях ввода в UserListSearch, чтобы это работало. Функция рендеринга моего UsersContainer теперь выглядит следующим образом:

class UserContainer extends React.Component {
  render() {
    return (
      <div>
        <Route
          exact
          path={this.props.match.url}
          render={() => (
            <div>
              <UserListSearch
                handleSearchChange={this.handleSearchChange}
                searchTerm={this.state.searchTerm}
              />
              <UserList
                isLoading={this.state.isLoading}
                users={this.props.users}
                user={this.state.user}
                handleNewUserClick={this.handleNewUserClick}
              />
            </div>
          )}
        />
      </div>  
    )
  }
}

Надеюсь, это кому-то тоже поможет.

Ответ 12

Оказывается, я связывал this с компонентом, который вызывал его повторное рендеринг.

Я решил опубликовать это здесь на случай, если у кого-то еще будет эта проблема.

Я должен был изменить

<Field
    label="Post Content"
    name="text"
    component={this.renderField.bind(this)}
/>

к

<Field
    label="Post Content"
    name="text"
    component={this.renderField}
/>

Простое исправление, так как в моем случае мне не нужно было this renderField в renderField, но, надеюсь, моя публикация поможет кому-то еще.

Ответ 13

Я переключил value prop в defaultValue. Это подходит для меня.

...
// before
<input value={myVar} />

// after
<input defaultValue={myVar} />

Ответ 14

Моя проблема заключалась в том, что я назвал свой ключ динамически со значением элемента, в моем случае "name", поэтому ключ был key = {${item.name}-${index}}. Поэтому, когда я хотел изменить ввод с помощью item.name в качестве значения, они также меняли ключ и, следовательно, реагировал, не распознавал этот элемент.