React.js-다시 렌더링 할 때 입력 포커스가 손실 됨


97

나는 단지 텍스트 입력에 쓰고 있고 onChange내가 호출하는 setState경우에 React가 내 UI를 다시 렌더링합니다. 문제는 텍스트 입력이 항상 포커스를 잃기 때문에 각 문자에 대해 다시 포커스가 필요하다는 것입니다. : 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>
        );
    }

});

발생하는 유일한 이유는 a) 다른 무언가가 초점을 훔치거나 b) 입력이 깜박이는 경우입니다. 문제를 보여주는 jsfiddle / jsbin을 제공 할 수 있습니까? 다음은 기본 반응 jsbin 입니다.
Brigand 2014 년

ㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ 코드가 일반 js에서 어떻게 보일지 확실하지 않습니다. 그러나 나는 당신이 :) 그것을 할 수 있습니다 확신
Medda86

1
@FakeRainBrigand : 깜박임이란 무엇을 의미합니까?
Krab 2014 년

@Krab, this.props.selected가 거짓이 된 다음 다시 참이되는 것처럼. 그러면 입력이 마운트 해제되고 다시 마운트됩니다.
Brigand

@Krab, slimScroll 라인을 제거해보십시오. 문제를 일으키는 이상한 일을 할 수 있습니다.
Brigand

답변:


87

나머지 코드를 보지 않고는 추측입니다. EditorContainer를 만들 때 구성 요소에 대한 고유 키를 지정합니다.

<EditorContainer key="editor1"/>

다시 렌더링이 발생할 때 동일한 키가 표시되면 React가 뷰를 방해하지 않고 다시 생성하지 않고 재사용하도록 지시합니다. 그러면 초점이 맞춰진 항목이 초점을 유지해야합니다.


2
이것은 입력 양식을 포함하는 하위 구성 요소를 렌더링하는 문제를 해결했습니다. 그러나 제 경우에는 그 반대가 필요했습니다. 원할 때 양식이 다시 렌더링되지 않았습니다. 키 속성 추가가 작동했습니다.
Sam Texas

2
입력에 키를 추가해도 도움이되지 않았습니다.
Mïchael

6
입력 내용을 포함하는 구성 요소도 다시 렌더링되었을 수 있습니다. 둘러싸는 구성 요소의 키를 확인하십시오.
Petr Gladkikh 2015 년

@PetrGladkikh의 공감 댓글입니다. 모든 주변 구성 요소에 키가 필요했습니다.
Brian Cragun

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

Ref. 솔루션 : https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947


1
나는 렌더링 함수 내에서 HOC를 사용하고 있었는데, HOC 호출을 구성 요소 정의로 이동하면 트릭이 발생했습니다. 이 답변과 어떻게 든 비슷합니다.
Mark E

3
나는 이것이 내 문제라고 생각하지만 난 문제가 구성 요소의 외부 이동 데 render()와이 class ... extends Component때문에에 대한 참조의를 this. 예onChange={this.handleInputChange}
Nateous

3
이야기의 사기는 React 클래스 구성 요소의 경우 render함수 내부에 구성 요소를 정의하지 않고 React 기능 구성 요소의 경우 함수 본문에 구성 요소를 정의하지 않습니다.
왕 지아 하우

1
감사! 당신은 내 엉덩이를 구했습니다!
K-Sato

1
@Nateous 나는 똑같은 상황을 겪었습니다. render'this.handleChange'메소드를 전달받은 함수 내에서 HOC 호출을 수행하고 사용할 구성 요소를 반환했습니다. 예 const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). 구성 요소 생성을 생성자로 옮기고 render메서드에서 this.TextAreaDataField. 매력처럼 작동했습니다.
Marcus Junius Brutus

36

A는 라우터 반응 내에서 문제가 있다면 <Route/>사용 render하는 대신 소품 component.

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


초점 손실은 component소품 React.createElement이 변경 사항을 다시 렌더링하는 대신 매번 사용 하기 때문에 발생합니다 .

세부 정보 : https://reacttraining.com/react-router/web/api/Route/component


2
이것은 본질적으로 내 문제였습니다. 구체적으로, 컴포넌트 prop에 익명 함수를 전달하여 focus-loss : <Route component={() => <MyComponent/>} />, 내가해야 할 때<Route component={MyComponent} />
Daniel

당신은 내 하루를 구했습니다-그렇게 간단합니다!
Fabricio

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"prop을 잃습니다.

내부 배열을 반응 구성 요소로 변환하는 문제 (렌더링 기능이있는 함수)를 수정했습니다.

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 객체를 읽고 특히 입력 텍스트가있는 열이있는 행을 만듭니다.

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

내 문제는 항목 값을 사용하여 동적으로 키 이름을 지정했다는 것입니다. 제 경우에는 "name"이므로 키는 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)}
/>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.