简体   繁体   English

Reactjs - 在动态元素渲染中添加ref到输入

[英]Reactjs - Adding ref to input in dynamic element render

I'm trying to focus/highlight input text onClick in React. 我正在尝试在React上关注/突出显示输入文本onClick。 It works as expected, but only on the last element in the rendered array. 它按预期工作,但仅适用于渲染数组中的最后一个元素。 I've tried several different methods but they all do the exact same thing. 我尝试了几种不同的方法,但它们都做了完全相同的事情。 Here are two examples of what I have: 以下是我的两个例子:

export default class Services extends Component {

handleFocus(event) {
    event.target.select()
}

handleClick() {
    this.textInput.focus()
}


render() {
    return (
        <div>
            {element.sources.map((el, i) => (
                <List.Item key={i}>
                <Segment style={{marginTop: '0.5em', marginBottom: '0.5em'}}>
                    <Input fluid type='text'
                        onFocus={this.handleFocus}
                        ref={(input) => { this.textInput = input }} 
                        value='text to copy'
                        action={
                            <Button inverted color='blue' icon='copy' onClick={() => this.handleClick}></Button>
                        }
                    />
                </Segment>
                </List.Item>
            ))}
        </div>
    )
}

If there's only one element being rendered, it focuses the text in the input, but if there are multiple elements, every element's button click selects only the last element's input. 如果只有一个元素被渲染,它会将文本聚焦在输入中,但如果有多个元素,则每个元素的按钮单击仅选择最后一个元素的输入。 Here's another example: 这是另一个例子:

export default class Services extends Component {

constructor(props) {
    super(props)

    this._nodes = new Map()
    this._handleClick = this.handleClick.bind(this)
}

handleFocus(event) {
    event.target.select()
}

handleClick(e, i) {
    const node = this._nodes.get(i)
    node.focus()
}


render() {
    return (
        <div>
            {element.sources.map((el, i) => (
                <List.Item key={i}>
                <Segment style={{marginTop: '0.5em', marginBottom: '0.5em'}}>
                    <Input fluid type='text'
                        onFocus={this.handleFocus}
                        ref={c => this._nodes.set(i, c)} 
                        value='text to copy'
                        action={
                            <Button inverted color='blue' icon='copy' onClick={e => this.handleClick(e, i)}></Button>
                        }
                    />
                </Segment>
                </List.Item>
            ))}
        </div>
    )
}

Both of these methods basically respond the same way. 这两种方法基本上都以相同的方式响应。 I need the handleClick input focus to work for every dynamically rendered element. 我需要handleClick输入焦点来适用于每个动态渲染的元素。 Any advice is greatly appreciated. 任何意见是极大的赞赏。 Thanks in advance! 提前致谢!

The Input component is imported from Semantic UI React with no additional implementations in my app Input组件是从Semantic UI React导入的,我的应用程序中没有其他实现

UPDATE Thanks guys for the great answers. 更新感谢大家的答案。 Both methods work great in a single loop element render, but now I'm trying to implement it with multiple parent elements. 这两种方法在单个循环元素渲染中都很有效,但现在我正在尝试使用多个父元素来实现它。 For example: 例如:

import React, { Component } from 'react'
import { Button, List, Card, Input, Segment } from 'semantic-ui-react'

export default class ServiceCard extends Component {

handleFocus(event) {
    event.target.select()
}

handleClick = (id) => (e) => {
    this[`textInput${id}`].focus()
}

render() {
    return (
        <List divided verticalAlign='middle'>
            {this.props.services.map((element, index) => (
                <Card fluid key={index}>
                    <Card.Content>
                        <div>
                            {element.sources.map((el, i) => (
                                <List.Item key={i}>
                                    <Segment>
                                        <Input fluid type='text'
                                            onFocus={this.handleFocus}
                                            ref={input => { this[`textInput${i}`] = input }} 
                                            value='text to copy'
                                            action={
                                                <Button onClick={this.handleClick(i)}></Button>
                                            }
                                        />
                                    </Segment>
                                </List.Item>
                            ))}
                        </div>
                    </Card.Content>
                </Card>
            ))}
        </List>
    )
}

Now, in the modified code, your methods work great for one Card element, but when there are multiple Card elements, it still only works for the last one. 现在,在修改后的代码中,您的方法适用于一个Card元素,但是当有多个Card元素时,它仍然只适用于最后一个。 Both Input Buttons work for their inputs respectively, but only on the last Card element rendered. 两个Input Buttons分别适用于它们的输入,但仅适用于最后渲染的Card元素。

You are setting a ref inside a loop, as you already know, the ref is set to the class via the this key word. 您正在循环中设置ref ,正如您所知, ref通过this关键字设置为class This means that you are setting multiple refs but overriding the same one inside the class . 这意味着,要设置多个refs ,但覆盖的内同一个class
One solution (not the ideal solution) is to name them differently, maybe add the key to each ref name: 一种解决方案(不是理想的解决方案)是以不同的方式命名它们,也许可以为每个ref名称添加密钥:

        ref={input => {
          this[`textInput${i}`] = input;
        }}

and when you target that onClick event of the Button you should use the same key as a parameter: 当你定位Button onClick事件时,你应该使用相同的键作为参数:

 action={
                  <Button
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick(i)}
                  >
                    Focus
                  </Button>
                }

Now, the click event should change and accept the id as a parameter and trigger the relevant ref (i'm using currying here): 现在,click事件应该更改并接受id作为参数并触发相关的ref (我在这里使用currying):

  handleClick = (id) => (e) => {
      this[`textInput${id}`].focus();
  }

Note that this is and easier solution but not the ideal solution, as we create a new instance of a function on each render, hence we pass a new prop which can interrupt the diffing algorithm of react (a better and more "react'ish" way coming next). 请注意,这是更简单的解决方案,但不是理想的解决方案,因为我们在每个渲染上创建一个新的函数实例,因此我们传递了一个新的prop,它可以中断反应的差异算法(更好,更“反应”)接下来的方式)。

Pros: 优点:

  • Easier to implement 更容易实施
  • Faster to implement 更快实施

Cons: 缺点:

  • May cause performance issues 可能会导致性能问题
  • Less the react components way 减少反应组分的方式

Working example 工作实例

This is the full Code: 这是完整的代码:

class Services extends React.Component {

  handleFocus(event) {
    event.target.select();
  }


  handleClick = id => e => {
    this[`textInput${id}`].focus();
  };

  render() {
    return (
      <div>
        {sources.map((el, i) => (
          <List.Item key={i}>
            <Segment style={{ marginTop: "0.5em", marginBottom: "0.5em" }}>
              <Input
                fluid
                type="text"
                onFocus={this.handleFocus}
                ref={input => {
                  this[`textInput${i}`] = input;
                }}
                value="text to copy"
                action={
                  <Button
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick(i)}
                  >
                    Focus
                  </Button>
                }
              />
            </Segment>
          </List.Item>
        ))}
      </div>
    );
  }
}

render(<Services />, document.getElementById("root"));

A better and more "react'ish" solution would be to use component composition or a HOC that wraps the Button and inject some simple logic, like pass the id instead of using 2 functions in the parent. 一个更好,更“反应”的解决方案是使用组件组合或包裹Button的HOC并注入一些简单的逻辑,比如传递id而不是在父级中使用2个函数。

Pros: 优点:

  • As mentioned, Less chances of performance issues 如上所述,性能问题的可能性较小
  • You can reuse this component and logic 您可以重用此组件和逻辑
  • Sometimes easier to debug 有时更容易调试

Cons: 缺点:

  • More code writing 更多代码编写

  • Another component to maintain / test etc.. 维护/测试等的另一个组件。

A working example 一个工作的例子
The full Code: 完整的代码:

class MyButton extends React.Component {

  handleClick = (e) =>  {
    this.props.onClick(this.props.id)
  }

  render() {
    return (
      <Button
      {...this.props}
        onClick={this.handleClick}
      >
        {this.props.children}
      </Button>
    )
  }
}


class Services extends React.Component {

  constructor(props){
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleFocus(event) {
    event.target.select();
  }


  handleClick(id){
    this[`textInput${id}`].focus();
  };

  render() {
    return (
      <div>
        {sources.map((el, i) => (
          <List.Item key={i}>
            <Segment style={{ marginTop: "0.5em", marginBottom: "0.5em" }}>
              <Input
                fluid
                type="text"
                onFocus={this.handleFocus}
                ref={input => {
                  this[`textInput${i}`] = input;
                }}
                value="text to copy"
                action={
                  <MyButton
                    inverted
                    color="blue"
                    icon="copy"
                    onClick={this.handleClick}
                    id={i}
                  >
                    Focus
                  </MyButton>
                }
              />
            </Segment>
          </List.Item>
        ))}
      </div>
    );
  }
}

render(<Services />, document.getElementById("root"));

Edit 编辑
As a followup to your edit: 作为编辑的后续内容:

but when there are multiple Card elements, it still only works for the last one. 但是当有多个Card元素时,它仍然只适用于最后一个。

This happens for the same reason as before, you are using the same i for both arrays. 出现这种情况的原因与之前相同,您对两个阵列使用相同的i
This is an easy solution, use both index and i for your ref names. 这是一个简单的解决方案,使用indexi作为您的ref名称。
Setting the ref name: 设置ref名称:

ref={input => { this[`textInput${index}${i}`] = input }}

Passing the name to the handler: 将名称传递给处理程序:

<Button onClick={this.handleClick(`${index}${i}`)}></Button>

Working example 工作实例

I've modified my question and provided a second solution that is considered best practice. 我修改了我的问题并提供了第二种被认为是最佳实践的解决方案。 read my answer again and see the different approaches. 再次阅读我的答案,看看不同的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM