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です。
ブリガンド

笑...ちょっと面倒そうですね:P jqueryを使用して、新しくレンダリングされたinputfiledの識別子を設定し、フォーカスを呼び出します。単純なjsでコードがどのように見えるかわかりません。しかし、私はあなたがそれを行うことができると確信しています:)
Medda86

1
@FakeRainBrigand:ちらつきとはどういう意味ですか?
Krab

@ Krab、this.props.selectedがfalseになり、その後再びtrueになるような場合。これにより、入力がアンマウントされてから、再度マウントされます。
ブリガンド

@ Krab、slimScroll行を削除してみてください。問題を引き起こしている奇妙なことをしている可能性があります。
ブリガンド2014年

回答:


86

残りのコードを見ることなく、これは推測です。EditorContainerを作成するときに、コンポーネントに一意のキーを指定します。

<EditorContainer key="editor1"/>

再レンダリングが発生したときに同じキーが表示された場合、これにより、Reactはビューを再作成せず、再利用せずに再利用します。次に、フォーカスされたアイテムはフォーカスを保持する必要があります。


2
これにより、入力フォームを含むサブコンポーネントのレンダリングに関する問題が解決されました。しかし、私の場合は、反対のものが必要でした。フォームは、希望どおりに再レンダリングされませんでした。キー属性の追加は機能しました。
サムテキサス

2
入力するキーを追加することは助けにはならなかった
マイケル・マカロフ

5
入力を含むコンポーネントも再レンダリングされた可能性があります。囲んでいるコンポーネントのキーを確認します。
Petr Gladkikh 2015

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

したがって、問題は、すべてを1か所で常にコーディングして、迅速にテストし、後ですべてを個別のモジュールに分割することです。ただし、ここでは、入力変更の状態を更新するとレンダリング機能がトリガーされ、フォーカスが失われるため、この戦略は逆効果になります。

修正は簡単で、最初からモジュール化を行う、つまり「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


1
私はrender関数内でHOCを使用していましたが、HOC呼び出しをコンポーネント定義に移動するとうまくいきました。どういうわけかこの答えに似ています。
Mark E

3
私は、これは私の問題だと思うが、私はトラブルのコンポーネントの外を移動した午前render()class ... extends Component理由を参照するのthis。例onChange={this.handleInputChange}
Nateous

3
ストーリーの士気は、Reactクラスコンポーネントの場合、render関数内のコンポーネントを定義しないでください。React機能コンポーネントの場合、関数本体でコンポーネントを定義しないでください。
Wong Jia Hau

1
ありがとう!あなたは私のお尻を救った!
K-Sato

1
@Nateous私はまったく同じ状況でした。render'this.handleChange'メソッドに渡された関数内のHOC呼び出しで、使用するコンポーネントが返されました。例えばconst TextAreaDataField = TextAreaDataFieldFactory(this.handleChange)。コンポーネントの作成をコンストラクターに移動し、renderメソッドとしてにアクセスするだけthis.TextAreaDataFieldです。魅力のように働いた。
Marcus Junius Brutus

36

それが反応ルーター内の問題である場合、の代わりに小道具を<Route/>使用してください。rendercomponent

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


componentプロップReact.createElementが変更を再レンダリングする代わりに毎回使用するため、フォーカスが失われます。

詳細はこちら:https : //reacttraining.com/react-router/web/api/Route/component


2
これは本質的に私の問題でした。具体的には、私は、コンポーネントの小道具に無名関数を渡すフォーカス損失をトリガした<Route component={() => <MyComponent/>} />、私がやった必要がある場合<Route component={MyComponent} />
ダニエル

あなたは私の日を救った-そのように簡単です!
Fabricio

12

私の答えは@ z5hが言ったことに似ています。

私の場合、コンポーネントのMath.random()一意を生成するkeyために使用しました。

keyその配列内のすべてのコンポーネントを再レンダリングするのではなく、特定のコンポーネントの再レンダリングをトリガーするためにのみ使用されると思いました(コードでコンポーネントの配列を返します)。再描画後に状態を復元するために使用されることを知りませんでした。

それを取り除くことは私にとって仕事をしました。


1
npmjs.com/package/shortidのようなモジュールも、このようなものを処理する良い方法です。
skwidbreth

4

要素にautoFocus属性を適用すると、inputフォーカスが必要な入力が1つしかない状況での回避策として実行できます。その場合、key属性は1つの要素に過ぎないため不要であり、さらに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参照が無効になるためです。つまり、reactによって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}
/>

私の場合、実際にはは必要なかったので、簡単な修正ですthisrenderField、うまくいけば、これを投稿して他の人の役に立つと思います。


0

valueプロップをに切り替えましたdefaultValue。それは私にとってはうまくいきます。

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

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

0

私の問題は、アイテムの値を使用してキーに動的に名前を付けることでした${item.name}-${index}。私の場合は「name」なので、キーはkey = { }でした。したがって、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.