React.js - втрачає фокус при введенні


97

Я просто пишу для введення тексту, і 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>
        );
    }

});

Єдина причина, що це могло б статися - це якщо а) щось інше вкраде фокус, або б) введення мерехтить. Чи можете ви надати jsfiddle / jsbin, що показує проблему? Ось база реагує jsbin .
Бриганд

ха-ха ... ну це звучить трохи надокучливо: P За допомогою jquery я б встановив ідентифікатор для нового відтвореного введеного файлу, а потім зателефонував фокус на нього. Не впевнений, як виглядав би код у звичайному js. Але я впевнений, що ти можеш це зробити :)
Medda86

1
@FakeRainBrigand: що ти маєш на увазі під мерехтінням?
Краб

@Krab, як якщо б цей.props.selected став хибним, а потім знову став істинним. Це призведе до відключення введення та знову встановлення.
Бриганд

@Krab, спробуйте видалити лінії slimScroll; що може робити щось дивне, що спричиняє проблеми.
Brigand

Відповіді:


87

Не бачачи решти коду, це здогад. Під час створення EditorContainer вкажіть унікальний ключ для компонента:

<EditorContainer key="editor1"/>

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


2
Це вирішило мою проблему із відображенням підкомпонента, що містить форму введення. Але в моєму випадку мені потрібно було навпаки - форма не передавалася, коли я цього хотів. Додано ключовий атрибут.
Сем Техас,

2
додавання ключа до входу не допомогло
Mïchael Makaröv

6
Можливо, компоненти (-и), які містять ваш вклад, також будуть відтворені. Перевірте клавіші на огороджувальних компонентах.
Петр Гладких

Коментар проти "@PetrGladkikh". Мені потрібні були ключі від усіх компонентів, що укладають.
Брайан Крегун,

49

Я постійно і знову повертаюся сюди і завжди знаходжу рішення у своїх інших місцях наприкінці. Отже, я задокументую це, бо знаю, що забуду це знову!

Причина 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/isissue/540#issuecomment-283664947


1
Я використовував HOC всередині функції візуалізації, переміщення виклику HOC на визначення компонента зробило свою справу. Якось схоже на цю відповідь.
Mark E

3
Я думаю, що це моя проблема, але у мене виникають проблеми з переміщенням компонентів за межами render()та class ... extends Componentчерез посилання на this. напр.onChange={this.handleInputChange}
Непорочний

3
Мораль розповіді для компонентів класу React не визначає компоненти всередині renderфункції, для функціональних компонентів React, не визначає компонентів у тілі функції.
Вонг Цзя Хау

1
Дякую! Ти врятував мій недопалок !!
K-Сато

1
@Nateous У мене була точно така ж ситуація. HOC викликає всередині renderфункції, яка передається методом "this.handleChange", і повертає компоненти, які будуть використані. Напр const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). Я просто перемістив створення компонента в конструктор і отримав доступ до них у renderметоді як this.TextAreaDataField. Працював як шарм.
Маркус Юній Брут

36

Якщо проблема в маршрутизаторі реагує, замість цього <Route/>використовуйте renderопору component.

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


Втрата фокусу трапляється тому, що componentвикористовується опораReact.createElement кожен раз замість того, щоб просто повторювати зміни.

Деталі тут: https://reacttraining.com/react-router/web/api/Route/component


2
Це було по суті моє питання. Зокрема, я передавав анонімну функцію до опорного компонента, викликаючи втрату фокусу:, <Route component={() => <MyComponent/>} />коли я повинен був це зробити<Route component={MyComponent} />
Даніель,

Ти врятував мій день - просто так!
Фабрикіо

12

Моя відповідь схожа на те, що сказав @ z5h.

У моєму випадку я використовував Math.random()для створення унікального keyкомпонента.

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

Видалення це зробило роботу для мене.


1
Модуль на зразок npmjs.com/package/shortid - це також хороший спосіб впоратися з чимось подібним.
skwidbreth

4

Застосування autoFocusатрибута до inputелемента може виконуватись як вирішення в ситуаціях, коли є лише один вхід, на який потрібно зосередити увагу. У цьому випадку keyатрибут був би непотрібним, оскільки це лише один елемент, і крім того, вам не доведеться турбуватися про розбиття inputелемента на його власний компонент, щоб уникнути втрати фокусу на повторному відтворенні основного компонента.


4

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

Проблема в моєму коді полягала в тому, що я створив вкладений масив елементів 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>
);

3

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


3

Я вирішив ту саму проблему, видаливши атрибут ключа у вхідних та його батьківських елементах

// Before
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    key={ Math.random }
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>
// After
<input
    className='invoice_table-input invoice_table-input-sm'
    type='number'
    defaultValue={pageIndex + 1}
    onChange={e => {
        const page = e.target.value ? Number(e.target.value) - 1 : 0
        gotoPage(page)
    }}
/>


2

Для мене це було викликано тим, що вікно пошуку вводиться в тому ж компоненті (званий 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>  
    )
  }
}

Сподіваємось, це теж допомагає комусь.


2

Якщо поле введення знаходиться всередині іншого елемента (тобто елемента контейнера типу <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- еліпси, що вказують на пропущений код), на елементі контейнера (як і в полі введення) повинен бути унікальний і постійний ключ . В іншому випадку React відображає абсолютно новий елемент контейнера, коли стан дитини оновлюється, а не просто рендерінг старого контейнера. Логічно, оновлювати слід лише дочірній елемент, але ...

У мене була ця проблема під час спроби написати компонент, який займав купу адресної інформації. Робочий код виглядає приблизно так

// import react, components
import React, { Component } from 'react'

// import various functions
import uuid from "uuid";

// import styles
import "../styles/signUp.css";

export default class Address extends Component {
  constructor(props) {
    super(props);
    this.state = {
      address1: "",
      address2: "",
      address1Key: uuid.v4(),
      address2Key: uuid.v4(),
      address1HolderKey: uuid.v4(),
      address2HolderKey: uuid.v4(),
      // omitting state information for additional address fields for brevity
    };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    event.preventDefault();
    this.setState({ [`${event.target.id}`]: event.target.value })
  }

  render() {
    return (
      <fieldset>
        <div className="labelAndField" key={this.state.address1HolderKey} >
          <label className="labelStyle" for="address1">{"Address"}</label>
          <input className="inputStyle"
            id="address1"
            name="address1"
            type="text"
            label="address1"
            placeholder=""
            value={this.state.address1}
            onChange={this.handleChange}
            key={this.state.address1Key} ></input >
        </div> 
        <div className="labelAndField" key={this.state.address2HolderKey} >
          <label className="labelStyle" for="address2">{"Address (Cont.)"}</label>
          <input className="inputStyle"
            id="address2"
            name="address2"
            type="text"
            label="address2"
            placeholder=""
            key={this.state.address2Key} ></input >
        </div>
        {/* omitting rest of address fields for brevity */}
      </fieldset>
    )
  }
}

Гостроокі читачі це відзначать <fieldset> він містить елемент, проте для нього не потрібен ключ. Те саме стосується <>і <React.Fragment>чи навіть <div>чому? Можливо, потрібен лише безпосередній контейнер. Я не знаю. Як кажуть підручники з математики, пояснення залишається читачеві як вправа.


1

Основна причина полягає в тому, що коли React повторно відображатиметься, ваша попередня посилання на DOM буде недійсною. Це означає, що реакція змінила дерево DOM, і ви this.refs.input.focus не працюватиме, оскільки введення тут вже не існує.


1

Надані відповіді мені не допомогли, ось що я зробив, але у мене була унікальна ситуація.

Для очищення коду я, як правило, використовую цей формат, поки не буду готовий перетягнути компонент в інший файл.

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>
)

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


1

У мене була та ж проблема з таблицею 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>
            );
          }
        }

0

Виявляється, я був обов'язковим this компонент, який викликав його рендеринг.

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

Мені довелося змінити

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

До

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

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


0

Я переключився на valueопору defaultValue. Це працює для мене.

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

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

0

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


0

У мене виникла ця проблема, і проблема виявилася в тому, що я використовую функціональний компонент і зв'язуюся зі станом батьківського компонента. Якщо я перейшов до використання компонента класу, проблема пішла. Сподіваємось, є спосіб обійти це при використанні функціональних компонентів, оскільки це набагато зручніше для простих візуалістів елементів та ін.


0

включив наступний код у введення тегу:

ref={(input) => {
     if (input) {
         input.focus();
     }
 }}

Перед:

<input
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"mail@mail.com"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>

Після:

<input
     ref={(input) => {
          if (input) {
             input.focus();
          }
      }}
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"mail@mail.com"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.