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 в качестве значения, они также меняли ключ и, следовательно, реагировал, не распознавал этот элемент.