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年

大声笑...好吧,这听起来有点烦人:P使用jquery,我将为新呈现的inputfiled设置一个标识符,然后对其进行调用。不知道代码在普通js中的样子。但我敢肯定您可以做到:)
Medda86

1
@FakeRainBrigand:您所说的闪烁是什么意思?
克拉(Krab)2014年

@Krab,就像this.props.selected变成了假,然后又变成了真。这将导致输入卸载,然后再次安装。
Brigand 2014年

@Krab,尝试删除slimScroll线;那可能是在做奇怪的事情而导致问题。
Brigand 2014年

Answers:


87

没有看到其余的代码,这是一个猜测。创建EditorContainer时,请为组件指定一个唯一键:

<EditorContainer key="editor1"/>

当发生重新渲染时,如果看到相同的键,这将告诉React不要破坏和重新生成视图,而是重新使用。然后,重点项目应保持重点。


2
这解决了我渲染包含输入表单的子组件的问题。但是在我的情况下,我需要相反的选择–表单没有按我的意愿重新渲染。添加key属性有效。
德州萨姆(Sam Texas)

2
增加键将输入没有帮助
迈克尔·马卡罗夫

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

参考 解决方案: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功能组件,不要在函数主体中定义组件。

1
谢谢!你救了我的屁股!
K-Sato

1
@Nateous我的情况完全一样。HOC在render正在传递的函数中调用“ this.handleChange”方法并返回要使用的组件。例如const TextAreaDataField = TextAreaDataFieldFactory(this.handleChange)。我干脆搬到部件创建的构造函数和访问它们的render方法this.TextAreaDataField。像魅力一样工作。
Marcus Junius Brutus

36

如果这是React Router内的问题,请<Route/>使用renderprop而不是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”道具

我修复了将内部数组转换为react组件(带有render函数的函数)的问题

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

我解决了删除输入中的key属性及其父元素的相同问题

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

简单修复,因为在我的情况下,我实际上并不需要thisin renderField,但是希望我发布此内容对其他人有帮助。


0

我将value道具切换为defaultValue。这对我行得通。

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

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

0

我的问题是我使用项目的值动态命名了密钥,在本例中为“名称”,因此密钥为key = { ${item.name}-${index}}。因此,当我想使用item.name作为值更改输入时,它们的键也将更改,因此react无法识别该元素


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)}
/>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.