React.js - masukan kehilangan fokus saat merender

101

Saya hanya menulis ke input teks dan onChangejika saya menelepon setState, maka React me-render UI saya. Masalahnya adalah input teks selalu kehilangan fokus, jadi saya perlu memfokuskannya lagi untuk setiap huruf: 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>
        );
    }

});
Krab
sumber
Satu-satunya alasan yang akan terjadi adalah jika a) ada hal lain yang mencuri fokus, atau b) input berkedip-kedip. Bisakah Anda memberikan jsfiddle / jsbin yang menunjukkan masalah? Berikut adalah basis reaksi jsbin .
Brigand
lol ... kedengarannya agak mengganggu: P Dengan jquery saya akan menetapkan pengenal untuk inputfiled baru yang diberikan dan kemudian memanggil fokus padanya. Tidak yakin bagaimana kode akan terlihat di js biasa. Tapi saya yakin Anda bisa melakukannya :)
Medda86
1
@FakeRainBrigand: apa yang Anda maksud dengan berkedip?
Krab
@Krab, seperti jika this.props.selected menjadi salah, lalu menjadi true lagi. Itu akan menyebabkan input di-unmount, dan kemudian me-mount lagi.
Brigand
@Krab, coba hapus garis slimSroll; itu bisa jadi melakukan sesuatu yang aneh yang menyebabkan masalah.
Brigand

Jawaban:

87

Tanpa melihat sisa kode Anda, ini adalah tebakan. Saat Anda membuat EditorContainer, tentukan kunci unik untuk komponen:

<EditorContainer key="editor1"/>

Saat rendering ulang terjadi, jika kunci yang sama terlihat, ini akan memberi tahu React jangan clobber dan meregenerasi tampilan, sebagai gantinya gunakan kembali. Kemudian item yang difokuskan harus mempertahankan fokus.

z5h
sumber
2
Ini memecahkan masalah saya dengan merender sub-komponen yang berisi formulir input. Tetapi dalam kasus saya, saya membutuhkan yang sebaliknya - formulir tidak dirender ulang ketika saya menginginkannya. Menambahkan atribut kunci berhasil.
Sam Texas
2
menambahkan kunci ke input tidak membantu
Mïchael Makaröv
6
Mungkin komponen yang berisi masukan Anda juga dirender ulang. Periksa kunci pada komponen penutup.
Petr Gladkikh
Suka komentar oleh @PetrGladkikh. Saya membutuhkan kunci pada semua komponen penutup.
Brian Cragun
51

Saya terus datang kembali ke sini lagi dan lagi dan selalu menemukan solusi saya di tempat lain pada akhirnya. Jadi, saya akan mendokumentasikannya di sini karena saya tahu saya akan melupakan ini lagi!

Alasan inputkehilangan fokus dalam kasus saya adalah karena fakta bahwa saya sedang merender ulang inputperubahan status.

Kode Buggy:

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

Jadi, masalahnya adalah saya selalu mulai mengkodekan semuanya di satu tempat untuk segera menguji dan kemudian memecah semuanya menjadi modul terpisah. Namun, di sini strategi ini menjadi bumerang karena memperbarui status pada perubahan input memicu fungsi render dan fokus hilang.

Perbaiki itu sederhana, lakukan modularisasi dari awal, dengan kata lain, "Pindahkan Inputkomponen dari renderfungsi"

Kode Tetap

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. ke solusinya: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947

Deepak
sumber
1
Saya menggunakan HOC di dalam fungsi render, memindahkan panggilan HOC ke definisi komponen melakukan trik. Entah bagaimana mirip dengan jawaban ini.
Tandai E
3
Saya rasa ini adalah masalah saya, tetapi saya mengalami masalah saat memindahkan Komponen ke luar render()dan class ... extends Componentkarena referensi ke this. misalnyaonChange={this.handleInputChange}
Nateous
3
Moral cerita, untuk komponen kelas React, jangan mendefinisikan komponen di dalam renderfungsi, untuk komponen fungsional React, jangan mendefinisikan komponen di badan fungsi.
Wong Jia Hau
1
Terima kasih! Kamu menyelamatkan pantatku !!
K-Sato
1
@Nateous Saya memiliki situasi yang persis sama. Panggilan HOC di dalam renderfungsi yang diteruskan metode 'this.handleChange' dan mengembalikan komponen yang akan digunakan. Mis const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange). Saya hanya memindahkan pembuatan komponen ke konstruktor dan mengaksesnya dalam rendermetode sebagai this.TextAreaDataField. Bekerja seperti pesona.
Marcus Junius Brutus
36

Jika itu masalah dalam router react, <Route/>gunakan renderprop sebagai ganti component.

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


Hilangnya fokus terjadi karena penggunaan componentpropReact.createElement setiap kali alih-alih hanya merender ulang perubahan.

Detailnya di sini: https://reacttraining.com/react-router/web/api/Route/component

spencer.sm
sumber
2
Ini pada dasarnya adalah masalah saya. Secara khusus, saya mengirimkan fungsi anonim ke prop komponen, memicu kehilangan fokus:, <Route component={() => <MyComponent/>} />ketika saya seharusnya melakukannya<Route component={MyComponent} />
Daniel
Anda menyelamatkan hari saya - sesederhana itu!
Fabricio
12

Jawaban saya mirip dengan apa yang dikatakan @ z5h.

Dalam kasus saya, saya biasa Math.random()menghasilkan unique keyuntuk komponen.

Saya pikir keyini hanya digunakan untuk memicu rerender untuk komponen tertentu daripada merender ulang semua komponen dalam array itu (saya mengembalikan array komponen dalam kode saya). Saya tidak tahu ini digunakan untuk memulihkan status setelah perenderan ulang.

Menghapus itu melakukan pekerjaan untuk saya.

Shankar Thyagarajan
sumber
1
Modul seperti npmjs.com/package/shortid juga merupakan cara yang bagus untuk menangani hal seperti ini.
skwidbreth
4

Menerapkan autoFocusatribut ke inputelemen dapat berfungsi sebagai solusi dalam situasi di mana hanya ada satu masukan yang perlu difokuskan. Dalam hal ini, keyatribut tidak diperlukan karena hanya satu elemen dan selanjutnya Anda tidak perlu khawatir tentang pemecahaninput elemen menjadi komponennya sendiri untuk menghindari kehilangan fokus pada render ulang komponen utama.

spakmad
sumber
4

Saya mendapat perilaku yang sama.

Masalah dalam kode saya adalah saya membuat Array bersarang dari elemen jsx seperti ini:

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

...

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

Setiap elemen dalam Array bersarang ini dirender ulang setiap kali saya memperbarui elemen induk. Dan input hilang di sana prop "ref" setiap waktu

Saya memperbaiki Masalah dengan mengubah array dalam menjadi komponen reaksi (fungsi dengan fungsi render)

const example = [
            <myComponentArray />
        ]

 ...

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

EDIT:

Masalah yang sama muncul saat saya membuat file bersarang React.Fragment

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

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

Saya baru saja mengalami masalah ini dan datang ke sini untuk meminta bantuan. Periksa CSS Anda! Bidang input tidak dapat memiliki user-select: none;atau tidak akan berfungsi di iPad.

David Sinclair
sumber
3

Saya memecahkan masalah yang sama dengan menghapus atribut key di input dan elemen induknya

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

Adrián Pedreira
sumber
2

Bagi saya, ini disebabkan oleh kotak input pencarian yang dirender dalam komponen yang sama (disebut UserList) sebagai daftar hasil pencarian. Jadi, setiap kali hasil pencarian berubah, seluruh komponen UserList dirender, termasuk kotak input.

Solusi saya adalah membuat komponen baru yang disebut UserListSearch yang terpisah dari UserList . Saya tidak perlu menyetel kunci pada kolom input di UserListSearch agar ini berfungsi. Fungsi render UsersContainer saya sekarang terlihat seperti ini:

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

Semoga ini membantu seseorang juga.

Dom Eden
sumber
2

Jika bidang masukan berada di dalam elemen lain (yaitu, elemen penampung seperti <div key={"bart"}...><input key={"lisa"}...> ... </input></div>- elipsis di sini menunjukkan kode yang dihilangkan), harus ada kunci unik dan konstan pada elemen penampung (serta di bidang masukan) . Selain itu, React merender elemen container baru saat status anak diperbarui, bukan hanya merender ulang container lama. Secara logis, hanya elemen anak yang harus diperbarui, tapi ...

Saya mengalami masalah ini saat mencoba menulis komponen yang mengambil banyak informasi alamat. Kode kerja terlihat seperti ini

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

Pembaca mata tajam akan melihat bahwa itu <fieldset>adalah elemen yang mengandung, namun tidak memerlukan kunci. Hal yang sama berlaku untuk<> dan <React.Fragment>atau bahkan <div>Mengapa? Mungkin hanya wadah segera yang membutuhkan kunci. Saya tidak tahu. Seperti yang dikatakan buku teks matematika, penjelasannya diserahkan kepada pembaca sebagai latihan.

Charles Goodwin
sumber
1

Alasan utamanya adalah: Saat React merender ulang, referensi DOM Anda sebelumnya tidak valid. Ini berarti react telah mengubah pohon DOM, dan Anda this.refs.input.focus tidak akan berfungsi, karena input di sini tidak ada lagi.

hlissnake
sumber
1

Jawaban yang diberikan tidak membantu saya, inilah yang saya lakukan tetapi saya memiliki situasi yang unik.

Untuk membersihkan kode saya cenderung menggunakan format ini sampai saya siap untuk menarik komponen ke file lain.

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

Tetapi ini menyebabkannya kehilangan fokus, ketika saya meletakkan kode langsung di div itu berhasil.

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

Saya tidak tahu mengapa ini terjadi, ini adalah satu-satunya masalah yang saya alami dengan menulis dengan cara ini dan saya melakukannya di sebagian besar file yang saya miliki, tetapi jika ada yang melakukan hal serupa, inilah mengapa ia kehilangan fokus.

Kevin Danikowski
sumber
1

Saya memiliki masalah yang sama dengan tabel html di mana saya memasukkan baris teks dalam kolom. di dalam lingkaran saya membaca objek json dan saya membuat baris khususnya saya memiliki kolom dengan inputtext.

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

Saya berhasil menyelesaikannya dengan cara berikut

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>
            );
          }
        }
Pabrik Perangkat Lunak
sumber
0

Ternyata saya mengikat thiskomponen yang menyebabkannya merender.

Saya pikir saya akan mempostingnya di sini jika ada orang lain yang memiliki masalah ini.

Saya harus berubah

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

Untuk

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

Memperbaiki sederhana karena dalam kasus saya, saya tidak benar-benar perlu thisdi renderField, tapi mudah-mudahan saya postingan kehendak ini membantu orang lain.

Matt D
sumber
0

Saya beralih valueke prop defaultValue. Itu berhasil untuk saya.

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

// after
<input defaultValue={myVar} />
chrisheyn
sumber
0

Masalah saya adalah saya menamai kunci saya secara dinamis dengan nilai item, dalam kasus saya "name" jadi kuncinya adalah key = { ${item.name}-${index}}. Jadi ketika saya ingin mengubah input dengan item.name sebagai nilainya, kunci mereka juga akan berubah dan karena itu bereaksi tidak akan mengenali elemen itu

Amogu
sumber
0

Saya mengalami masalah ini dan masalahnya ternyata saya menggunakan komponen fungsional dan menghubungkan dengan status komponen induk. Jika saya beralih menggunakan komponen kelas, masalahnya hilang. Mudah-mudahan ada jalan keluar saat menggunakan komponen fungsional karena jauh lebih nyaman untuk perender barang sederhana dkk.

Kyle Crossman
sumber
-1

menyertakan kode berikutnya dalam masukan tag:

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

Sebelum:

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

Setelah:

<input
     ref={(input) => {
          if (input) {
             input.focus();
          }
      }}
      defaultValue={email}
      className="form-control"
      type="email"
      id="email"
      name="email"
      placeholder={"[email protected]"}
      maxLength="15"
      onChange={(e) => validEmail(e.target.value)}
/>
Thiago Pires
sumber