React.js - đầu vào mất tiêu điểm khi kết xuất


97

Tôi chỉ viết vào văn bản nhập và trong onChangetrường hợp tôi gọi setState, vì vậy React kết xuất giao diện người dùng của tôi. Vấn đề là đầu vào văn bản luôn mất tiêu điểm, vì vậy tôi cần lấy nét lại cho mỗi chữ cái: 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>
        );
    }

});

Lý do duy nhất sẽ xảy ra là nếu a) thứ gì đó khác đánh cắp tiêu điểm hoặc b) đầu vào bị chập chờn. Bạn có thể cung cấp jsfiddle / jsbin hiển thị sự cố không? Đây là một jsbin phản ứng cơ sở .
Brigand

lol ... nghe có vẻ hơi khó chịu: P Với jquery, tôi sẽ đặt một mã định danh cho đầu vào kết xuất mới được điền vào và sau đó gọi tiêu điểm vào nó. Không chắc mã sẽ trông như thế nào trong js đơn giản. Nhưng tôi chắc chắn rằng bạn có thể làm điều đó :)
Medda86

1
@FakeRainBrigand: nhấp nháy nghĩa là gì?
Krab

@Krab, giống như nếu this.props.selected đang trở thành sai, và sau đó lại trở thành đúng. Điều đó sẽ khiến đầu vào bị ngắt kết nối và sau đó gắn kết lại.
Brigand

@Krab, hãy thử xóa các dòng slimScroll; đó có thể là làm điều gì đó kỳ lạ gây ra vấn đề.
Brigand

Câu trả lời:


86

Nếu không nhìn thấy phần còn lại của mã của bạn, đây là một phỏng đoán. Khi bạn tạo một EditorContainer, hãy chỉ định một khóa duy nhất cho thành phần:

<EditorContainer key="editor1"/>

Khi kết xuất lại xảy ra, nếu cùng một khóa được nhìn thấy, điều này sẽ thông báo cho React không chặn và tạo lại chế độ xem, thay vào đó sử dụng lại. Sau đó, mục tiêu điểm sẽ giữ lại tiêu điểm.


2
Điều này đã giải quyết vấn đề của tôi với việc hiển thị một thành phần con có chứa một biểu mẫu đầu vào. Nhưng trong trường hợp của tôi, tôi cần điều ngược lại - biểu mẫu không hiển thị lại khi tôi muốn. Thêm thuộc tính khóa đã hoạt động.
Sam Texas

2
thêm chìa khóa dẫn đến đầu vào đã không giúp đỡ
Michael Makarov

5
Có thể (các) thành phần chứa đầu vào của bạn cũng được hiển thị lại. Kiểm tra các phím trên các thành phần bao quanh.
Petr Gladkikh

Bình luận ủng hộ bởi @PetrGladkikh. Tôi cần các phím trên tất cả các thành phần bao quanh.
Brian Cragun

49

Tôi tiếp tục quay lại đây nhiều lần và luôn tìm ra giải pháp cho những nơi khác của tôi ở cuối. Vì vậy, tôi sẽ ghi lại nó ở đây vì tôi biết tôi sẽ quên điều này một lần nữa!

Lý do inputmất tập trung trong trường hợp của tôi là do tôi đang hiển thị lại inputtrạng thái thay đổi.

Mã lỗi:

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

Vì vậy, vấn đề là tôi luôn bắt đầu viết mã mọi thứ tại một nơi để nhanh chóng kiểm tra và sau đó chia nhỏ tất cả thành các mô-đun riêng biệt. Tuy nhiên, ở đây chiến lược này phản tác dụng vì việc cập nhật trạng thái trên thay đổi đầu vào sẽ kích hoạt chức năng hiển thị và tiêu điểm bị mất.

Cách khắc phục rất đơn giản, hãy thực hiện modul hóa lại từ đầu, hay nói cách khác là "Di chuyển Inputlinh kiện ra khỏi renderchức năng"

Mã cố định

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

Tham khảo giải pháp: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947


1
Tôi đang sử dụng HOC bên trong hàm kết xuất, việc di chuyển lệnh gọi HOC sang định nghĩa thành phần đã thực hiện một mẹo nhỏ. Bằng cách nào đó tương tự với câu trả lời này.
Đánh dấu E

3
Tôi nghĩ đây là vấn đề của tôi, nhưng tôi đang gặp sự cố khi di chuyển các Thành phần ra bên ngoài render()class ... extends Componentvì tham chiếu tới this. ví dụonChange={this.handleInputChange}
Nateous

3
Tinh thần của câu chuyện, đối với các thành phần lớp React, không xác định các thành phần bên trong renderhàm, đối với các thành phần chức năng React, không xác định các thành phần trong thân hàm.
Wong Jia Hau

1
Cảm ơn! Bạn đã cứu mông của tôi !!
K-Sato

1
@Nateous Tôi cũng gặp trường hợp tương tự. HOC gọi bên trong renderhàm đang được truyền phương thức 'this.handleChange' và trả về các thành phần sẽ được sử dụng. Vd const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). Tôi chỉ cần chuyển phần tạo thành phần sang phương thức khởi tạo và truy cập chúng trong renderphương thức dưới dạng this.TextAreaDataField. Làm việc như người ở.
Marcus Junius Brutus

36

Nếu đó là sự cố bên trong bộ định tuyến phản ứng, hãy <Route/>sử dụng phần mềm renderhỗ trợ thay vì component.

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


Việc mất tiêu điểm xảy ra vì bộ phận componentchống đỡ sử dụng React.createElementmỗi lần thay vì chỉ hiển thị lại các thay đổi.

Thông tin chi tiết tại đây: https://reacttraining.com/react-router/web/api/Route/component


2
Về cơ bản đây là vấn đề của tôi. Cụ thể, tôi đã chuyển một chức năng ẩn danh cho thành phần chống đỡ, gây ra hiện tượng mất tiêu điểm:, <Route component={() => <MyComponent/>} />khi đáng lẽ tôi nên làm<Route component={MyComponent} />
Daniel

Bạn đã cứu một ngày của tôi - đơn giản như vậy!
Fabricio

12

Câu trả lời của tôi tương tự như những gì @ z5h đã nói.

Trong trường hợp của tôi, tôi đã từng Math.random()tạo mộtkey cho thành phần.

Tôi nghĩ key chỉ được sử dụng để kích hoạt một kết xuất cho thành phần cụ thể đó thay vì hiển thị lại tất cả các thành phần trong mảng đó (tôi trả về một mảng các thành phần trong mã của mình). Tôi không biết nó được sử dụng để khôi phục trạng thái sau khi kết xuất.

Xóa nó đã làm công việc cho tôi.


1
Một mô-đun như npmjs.com/package/shortid cũng là một cách hay để xử lý những thứ như thế này.
skwidbreth

4

Việc áp dụng autoFocusthuộc tính cho inputphần tử có thể thực hiện như một giải pháp thay thế trong các trường hợp chỉ có một đầu vào cần được tập trung. Trong trường hợp đó, một keythuộc tính sẽ là không cần thiết vì nó chỉ là một phần tử và hơn nữa bạn sẽ không phải lo lắng về việc chia inputphần tử đó thành thành phần của chính nó để tránh mất tập trung vào việc kết xuất lại thành phần chính.


4

Tôi có hành vi tương tự.

Vấn đề trong mã của tôi là tôi đã tạo một Mảng lồng nhau của các phần tử jsx như sau:

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

...

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

Mọi phần tử trong Mảng lồng nhau này sẽ hiển thị mỗi khi tôi cập nhật phần tử mẹ. Và do đó, các yếu tố đầu vào mất đi chỗ dựa "ref" mỗi khi

Tôi đã khắc phục sự cố với việc chuyển đổi mảng bên trong thành một thành phần phản ứng (một hàm có chức năng kết xuất)

const example = [
            <myComponentArray />
        ]

 ...

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

BIÊN TẬP:

Vấn đề tương tự cũng xuất hiện khi tôi xây dựng một React.Fragment

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

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

3

Tôi vừa gặp phải vấn đề này và đến đây để được giúp đỡ. Kiểm tra CSS của bạn! Trường nhập không thể có user-select: none;hoặc sẽ không hoạt động trên iPad.


3

Tôi đã giải quyết vấn đề tương tự khi xóa thuộc tính khóa trong đầu vào và các phần tử mẹ của anh ấy

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

Đối với tôi, điều này là do hộp nhập tìm kiếm được hiển thị trong cùng một thành phần (được gọi là UserList) như danh sách kết quả tìm kiếm. Vì vậy, bất cứ khi nào kết quả tìm kiếm thay đổi, toàn bộ thành phần UserList sẽ hiển thị, bao gồm cả hộp nhập.

Giải pháp của tôi là tạo một thành phần hoàn toàn mới có tên là UserListSearch tách biệt với UserList . Tôi không cần đặt khóa trên các trường nhập trong UserListSearch để điều này hoạt động. Chức năng kết xuất của UsersContainer của tôi bây giờ trông như thế này:

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

Hy vọng rằng điều này sẽ giúp ai đó quá.


2

Nếu trường đầu vào nằm bên trong phần tử khác (nghĩa là phần tử vùng chứa như <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- dấu chấm lửng ở đây cho biết mã bị bỏ qua), thì phải có một khóa duy nhất và không đổi trên phần tử vùng chứa (cũng như trên trường đầu vào) . Ngược lại, React hiển thị một phần tử vùng chứa hoàn toàn mới khi trạng thái của phần tử con được cập nhật thay vì chỉ hiển thị lại vùng chứa cũ. Về mặt logic, chỉ phần tử con nên được cập nhật, nhưng ...

Tôi đã gặp sự cố này khi cố gắng viết một thành phần có nhiều thông tin địa chỉ. Mã làm việc trông như thế này

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

Những độc giả tinh mắt sẽ lưu ý rằng đó <fieldset>là một phần tử có chứa, nhưng nó không yêu cầu khóa. Điều tương tự cho <><React.Fragment>hoặc thậm chí <div>Tại sao? Có lẽ chỉ thùng chứa ngay lập tức cần một chìa khóa. Tôi không biết. Như sách giáo khoa toán nói, lời giải thích được để cho người đọc như một bài tập.


1

Lý do cốt lõi là: Khi React re-render, DOM ref trước đó của bạn sẽ không hợp lệ. Nó có nghĩa là phản ứng đã thay đổi cây DOM và bạn this.refs.input.focus sẽ không hoạt động, bởi vì đầu vào ở đây không tồn tại nữa.


1

Các câu trả lời được cung cấp không giúp được gì cho tôi, đây là những gì tôi đã làm nhưng tôi có một tình huống duy nhất.

Để làm sạch mã, tôi có xu hướng sử dụng định dạng này cho đến khi tôi sẵn sàng kéo thành phần vào một tệp khác.

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

Nhưng điều này khiến nó mất tập trung, khi tôi đặt mã trực tiếp vào div thì nó hoạt động.

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

Tôi không biết tại sao lại như vậy, đây là vấn đề duy nhất tôi gặp phải khi viết nó theo cách này và tôi làm điều đó trong hầu hết các tệp mà tôi có, nhưng nếu ai đó làm điều tương tự thì đây là lý do tại sao nó mất tập trung.


1

Tôi đã gặp vấn đề tương tự với một bảng html trong đó tôi có các dòng văn bản đầu vào trong một cột. bên trong một vòng lặp, tôi đọc một đối tượng json và tôi tạo các hàng, cụ thể là tôi có một cột với inputtext.

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

Tôi đã giải quyết nó theo cách sau

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

Hóa ra tôi đã liên kết thisvới thành phần khiến nó kết xuất.

Tôi đã nghĩ rằng tôi sẽ đăng nó ở đây trong trường hợp bất kỳ ai khác gặp vấn đề này.

Tôi đã phải thay đổi

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

Đến

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

Cách khắc phục đơn giản vì trong trường hợp của tôi, tôi thực sự không cần thisđến renderField, nhưng hy vọng tôi đăng bài này sẽ giúp được người khác.


0

Tôi chuyển sang valuechống đỡ để defaultValue. Nó ổn với tôi.

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

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

0

Vấn đề của tôi là tôi đã đặt tên động cho khóa của mình với một giá trị của mục, trong trường hợp của tôi là "tên" nên khóa là key = { ${item.name}-${index}}. Vì vậy, khi tôi muốn thay đổi đầu vào với item.name làm giá trị, khóa của chúng cũng sẽ thay đổi và do đó phản ứng sẽ không nhận ra phần tử đó


0

Tôi gặp sự cố này và sự cố hóa ra là tôi đang sử dụng một thành phần chức năng và liên kết với trạng thái của thành phần mẹ. Nếu tôi chuyển sang sử dụng một thành phần lớp, vấn đề đã biến mất. Hy vọng rằng có một cách giải quyết vấn đề này khi sử dụng các thành phần chức năng vì nó thuận tiện hơn rất nhiều cho các trình kết xuất vật phẩm đơn giản và cộng sự.


0

bao gồm mã tiếp theo trong đầu vào thẻ:

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

Trước:

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

Sau:

<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)}
/>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.