【问题标题】:Pasting of text is being duplicated in textbox in React Component在 React 组件的文本框中复制文本的粘贴
【发布时间】:2017-06-15 03:46:15
【问题描述】:

我不知道为什么粘贴到我的电子邮件后,它会在文本框中重复它。

screencast of the problem

似乎当我粘贴时,它正确触发了handleEmailPaste,但我也注意到handleEmailPaste 也被过去触发了,所以不知道为什么。我想粘贴是一种变化,所以在文本中粘贴会触发这两个功能可能是有道理的。 如果我注释掉handleEmailInput 中的代码,并粘贴一个值,它不会重复它。

我想我不知道处理这个问题的正确方法。在我看来,我确实需要两个单独的处理程序。请注意,我使用的是引导控件,并且在其上设置了 onChange 和 onPaste:

<FormControl
              bsSize="small"
              className="ft-username"
              componentClass="input"
              onPaste={this.props.handleEmailPaste}
              onChange={this.props.handleEmailInput}
              placeholder="Enter email"
              style={{ width: 300}}
              type="email"
              value={this.props.email}
            />

登录容器

import { connect } from 'react-redux'
import React, { Component } from 'react'
const zxcvbn = require('zxcvbn'),
  _ = require('lodash')

import * as AsyncActions from '../actions/Auth/AuthAsyncActions'
import Login from '../components/Login/Login'



class LoginContainer extends Component {
  constructor(props) {
    super(props)
    this.state = {
      email: '',
      password: '',
      errorMessage: '',
      emailValidationState: null,
      formIsValid: false,
      formValidationState: null,
      passwordValidationState: null,
      passwordIsValid: null
    }

    this.handleEmailPaste = this.handleEmailPaste.bind(this)
    this.handleEmailInput = this.handleEmailInput.bind(this)
    this.handlePasswordInput = this.handlePasswordInput.bind(this)
    this.handleLoginPressed = this.handleLoginPressed.bind(this)
    this.resetFields = this.resetFields.bind(this)
    this.validateForm = this.validateForm.bind(this)
    this.validateEmail = this.validateEmail.bind(this)
    this.validatePassword = this.validatePassword.bind(this)
  }

  handlePasswordInput(e) {
    const password = e.target.value
    this.setState({ password: password})
    this.validatePassword()
  }

  handleEmailPaste(e){
    console.log(`handleEmailPaste: ${e.clipboardData.getData('Text')}`)
    const value = e.clipboardData.getData('Text')
    this.setState({ email: value })
    this.validateEmail(value)
  }

  handleEmailInput(e) {
    this.setState({ email: e.target.value })
    this.validateEmail()
  }

  async handleLoginPressed(e) {
    e.preventDefault()
    this.validateForm()

    await this.props.authenticate(this.state.email, this.state.password)
    if(this.props.isAuthenticated) {
      this.props.history.push('/dashboard')
      return
    }

    if(!this.props.isAuthenticated){
      this.setState({
        formValidationState: 'error',
        errorMessage: this.state.formIsValid &&
        'Your password and/or email is not associated with an active user'
      })

      if(this.state.email && this.state.password){this.resetFields()}
    }
  }

  validateForm(){
    this.validateEmail()
    this.validatePassword()
    this.setState({
      formIsValid: (this.state.emailValidationState === 'success'
        && this.state.passwordValidationState === 'success')})
  }

  validatePassword(){
    const password = zxcvbn(this.state.password)
    if(password.score >=0){
      this.setState({
        passwordValidationState: 'error',
        passwordHelpText: password.feedback.suggestions})
      return
    }

    this.setState({
      passwordValidationState: 'success',
      passwordHelpText: null })
  }

  validateEmail(value){
    if((!_.isEmpty(value)) || !_.isEmpty(this.state.email)) {
      this.setState({
        emailValidationState: 'success',
        emailError: ''
      })
      return
    }

    this.setState({
      emailValidationState: 'error',
      emailError: 'please enter an email address'
    })
  }

    resetFields(){
    this.setState({
      email: '',
      emailError: '',
      emailValidationState: null,
      password: '',
      passwordHelpText: '',
      passwordValidationState: null })
  }

  render(){
    return(
      <div>
        <Login
          email={this.state.email}
          emailError={this.state.emailError}
          emailValidationState={this.state.emailValidationState}
          errorMessage={this.state.errorMessage}
          formValidationState={this.state.formValidationState}
          handleEmailInput={this.handleEmailInput}
          handleEmailPaste={this.handleEmailPaste}
          handlePasswordInput={this.handlePasswordInput}
          login={this.handleLoginPressed}
          password={this.state.password}
          passwordHelpText={this.state.passwordHelpText}
          passwordValidationState={this.state.passwordValidationState}
        />
      </div>
      )
  }
}

const mapStateToProps = state => ({
  isAuthenticating: state.auth.isAuthenticating,
  isAuthenticated: state.auth.isAuthenticated,
  token: state.auth.token
})

export const mapDispatchToProps = {
  authenticate: AsyncActions.authenticate
}

export { Login }
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)

登录

import React, {Component} from 'react'

import LoginForm from './LoginForm'

export default class Login extends Component {
  render(){
      return (
        <div>
          <LoginForm
            email={this.props.email}
            emailError={this.props.emailError}
            emailValidationState={this.props.emailValidationState}
            errorMessage={this.props.errorMessage}
            formValidationState={this.props.formValidationState}
            handleEmailInput={this.props.handleEmailInput}
            handleEmailPaste={this.props.handleEmailPaste}
            handlePasswordInput={this.props.handlePasswordInput}
            login={this.props.login}
            password={this.props.password}
            passwordHelpText={this.props.passwordHelpText}
            passwordValidationState={this.props.passwordValidationState}
          />
        </div>
      )
  }
}

登录表单

import React, { Component } from 'react'
import {
  Button,
  ControlLabel,
  HelpBlock,
  FormControl,
  FormGroup,
  PageHeader } from 'react-bootstrap'

export default class LoginForm extends Component {
  render(){
    return (
      <div className='ft-login-form'>
        <PageHeader className='ft-header'><small>Login</small></PageHeader>
        <form onSubmit={this.props.login}>
          <FormGroup validationState={this.props.formValidationState}>
            <ControlLabel className="ft-form-error-message">{this.props.errorMessage}</ControlLabel>
          </FormGroup>
          <FormGroup controlId="formBasicText" validationState={this.props.emailValidationState}>
            <ControlLabel>Email</ControlLabel>
            <FormControl
              bsSize="small"
              className="ft-username"
              componentClass="input"
              onPaste={this.props.handleEmailPaste}
              onChange={this.props.handleEmailInput}
              placeholder="Enter email"
              style={{ width: 300}}
              type="email"
              value={this.props.email}
            />
            <HelpBlock className="ft-email-error">{this.props.emailError}</HelpBlock>
          </FormGroup>
          <FormGroup validationState={this.props.passwordValidationState}>
            <ControlLabel>Password</ControlLabel>
            <FormControl
              bsSize="small"
              className="ft-password"
              componentClass="input"
              onPaste={this.props.handleEmailPaste}
              onChange={this.props.handlePasswordInput}
              placeholder="Enter password"
              style={{ width: 300}}
              type="password"
              value={this.props.password}
            />
            <HelpBlock className="ft-password-help-text">{this.props.passwordHelpText}</HelpBlock>
          </FormGroup>
          <Button
            className='ft-login-button'
            type='submit'
          >Login</Button>
        </form>
      </div>)
  }
}

更新

所以我添加了这个,这解决了问题:

 handleEmailInput(e) {
    if(!this.state.email) {
      this.setState({email: e.target.value})
    }
    this.validateEmail()
  }

基本上在这里我是说嘿,如果有人最初粘贴它,它会点击handleEmailPaste which setsState for email 所以如果是这样的话,(我知道handleEmailInput也将被那个粘贴触发/change),那么如果 handleEmailPaste 已经设置了它,我不想再次设置状态。相反,如果用户输入一个值而不是粘贴它,那么这个 if 语句将是 bypassed,因此是 handleEmailInput would setState in that case

但是我对电子邮件输入的整个处理对我来说感觉很糟糕。如果您认为这是一种 hack,并且对重构此代码有更好的想法,请告诉我。

更新 #2

废话,我注意到我没有得到重复,但现在我无法在电子邮件框中输入新值,它不会让我,它只是与填充的电子邮件一起坐在那里,但我无法修改它。

更新 #3 (咆哮:为什么 * 不让帖子正文文本区域的高度变大,当我滚动时我几乎看不到我在做什么)

所以真的我的问题不再是它复制我粘贴的文本的问题。我回到第一方,我原来的问题是我可以粘贴一些东西,但它使输入无效,我不知道为什么。我想我应该更新这篇文章的标题,但是哦。

无论如何,我只使用onChange 并摆脱了onPaste。没有更多重复发生

所以问题是:当您第一次将值粘贴到电子邮件文本框时的初始行为。当您第一次粘贴一个值时,我看到的行为(以下是没有 onPaste 的更新代码):

  1. 当你第一次粘贴时,它会命中我的handleEmailInput() 方法。
  2. handleEmailInput 调用 setState({email: e.target.value}) 所以你会认为这是为 this.state.email 设置的
  3. 但是当handleEmailInput 紧接着validateEmail 调用validateEmail() 时,它会检查this.state.email 并且出于某种原因它仍然 ""。因此,它最终会遇到将其设置为无效的第二个 setState

第一次调用this.setState({email: e.target.value }) 不是将this.state.email 设置为粘贴的电子邮件吗?我知道,当我在该行设置断点时,e.target.value 确实 有我粘贴的电子邮件,但是 this.setState({email: e.target.value }) 被调用完成后,出于某种原因在 validateEmail() 中,this.state.email 仍然收到"",我不明白为什么。也许这是 React 及其生命周期的基础?或者其他一些我还不知道的基础知识..不确定。

登录容器 (我已经完全删除了 onPaste 逻辑)

import { connect } from 'react-redux'
import React, { Component } from 'react'
const zxcvbn = require('zxcvbn'),
  _ = require('lodash')

import * as AsyncActions from '../actions/Auth/AuthAsyncActions'
import Login from '../components/Login/Login'

class LoginContainer extends Component {
  constructor(props) {
    super(props)
    this.state = {
      email: '',
      password: '',
      errorMessage: '',
      emailValidationState: null,
      formIsValid: false,
      formValidationState: null,
      passwordValidationState: null,
      passwordIsValid: null
    }

    this.handleEmailInput = this.handleEmailInput.bind(this)
    this.handlePasswordInput = this.handlePasswordInput.bind(this)
    this.handleLoginPressed = this.handleLoginPressed.bind(this)
    this.resetFields = this.resetFields.bind(this)
    this.validateForm = this.validateForm.bind(this)
    this.validateEmail = this.validateEmail.bind(this)
    this.validatePassword = this.validatePassword.bind(this)
  }

  handlePasswordInput(e) {
    const password = e.target.value
    this.setState({ password: password})
    this.validatePassword()
  }

  handleEmailInput(e) {
    this.setState({email: e.target.value })
    this.validateEmail()
  }

  async handleLoginPressed(e) {
    e.preventDefault()
    this.validateForm()

    await this.props.authenticate(this.state.email, this.state.password)
    if(this.props.isAuthenticated) {
      this.props.history.push('/dashboard')
      return
    }

    if(!this.props.isAuthenticated){
      this.setState({
        formValidationState: 'error',
        errorMessage: this.state.formIsValid &&
        'Your password and/or email is not associated with an active user'
      })

      if(this.state.email && this.state.password){this.resetFields()}
    }
  }

  validateForm(){
    this.validateEmail()
    this.validatePassword()
    this.setState({
      formIsValid: (this.state.emailValidationState === 'success'
        && this.state.passwordValidationState === 'success')})
  }

  validatePassword(){
    const password = zxcvbn(this.state.password)
    if(password.score >=0){
      this.setState({
        passwordValidationState: 'error',
        passwordHelpText: password.feedback.suggestions})
      return
    }

    this.setState({
      passwordValidationState: 'success',
      passwordHelpText: null })
  }

  validateEmail(){
    if(!_.isEmpty(this.state.email)) {
      this.setState({
        emailValidationState: 'success',
        emailError: ''
      })

      return
    }

      this.setState({
        emailValidationState: 'error',
        emailError: 'please enter an email address'
      })
  }

  resetFields(){
    this.setState({
      email: '',
      emailError: '',
      emailValidationState: null,
      password: '',
      passwordHelpText: '',
      passwordValidationState: null })
  }

  render(){
    return(
      <div>
        <Login
          email={this.state.email}
          emailError={this.state.emailError}
          emailValidationState={this.state.emailValidationState}
          errorMessage={this.state.errorMessage}
          formValidationState={this.state.formValidationState}
          handleEmailInput={this.handleEmailInput}
          handlePasswordInput={this.handlePasswordInput}
          login={this.handleLoginPressed}
          password={this.state.password}
          passwordHelpText={this.state.passwordHelpText}
          passwordValidationState={this.state.passwordValidationState}
        />
      </div>
      )
  }
}

const mapStateToProps = state => ({
  isAuthenticating: state.auth.isAuthenticating,
  isAuthenticated: state.auth.isAuthenticated,
  token: state.auth.token
})

export const mapDispatchToProps = {
  authenticate: AsyncActions.authenticate
}

export { Login }
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)

import React, { Component } from 'react'
import {
  Button,
  ControlLabel,
  HelpBlock,
  FormControl,
  FormGroup,
  PageHeader } from 'react-bootstrap'

登录表单 (请注意,我们只使用 onChange 来处理电子邮件)

export default class LoginForm extends Component {
  render(){
    return (
      <div className='ft-login-form'>
        <PageHeader className='ft-header'><small>Login</small></PageHeader>
        <form onSubmit={this.props.login}>
          <FormGroup validationState={this.props.formValidationState}>
            <ControlLabel className="ft-form-error-message">{this.props.errorMessage}</ControlLabel>
          </FormGroup>
          <FormGroup controlId="formBasicText" validationState={this.props.emailValidationState}>
            <ControlLabel>Email</ControlLabel>
            <FormControl
              bsSize="small"
              className="ft-username"
              componentClass="input"
              onChange={this.props.handleEmailInput}
              placeholder="Enter email"
              style={{ width: 300}}
              type="email"
              value={this.props.email}
            />
            <HelpBlock className="ft-email-error">{this.props.emailError}</HelpBlock>
          </FormGroup>
          <FormGroup validationState={this.props.passwordValidationState}>
            <ControlLabel>Password</ControlLabel>
            <FormControl
              bsSize="small"
              className="ft-password"
              componentClass="input"
              onPaste={() => this.props.handleEmailPaste}
              onChange={() => this.props.handlePasswordInput}
              placeholder="Enter password"
              style={{ width: 300}}
              type="password"
              value={this.props.password}
            />
            <HelpBlock className="ft-password-help-text">{this.props.passwordHelpText}</HelpBlock>
          </FormGroup>
          <Button
            className='ft-login-button'
            type='submit'
          >Login</Button>
        </form>
      </div>)
  }
}

【问题讨论】:

  • 您无法修改它,因为您的email 是一个Controlled Component,其值取决于state,由于if,它不会更新
  • 好的..如果我取出if,那么它会复制那个框中的值
  • 是的,如果是这样的话,我会被卡住,因为我不知道如何摆脱原来的问题,如果是这样的话,而我粘贴在电子邮件中,它会复制它(不知何故它正在更新状态两次或什么的,我真的不知道)
  • 我能够在JSFiddle 上重现问题,也许让我们一起解决 :)
  • 分享房间的网址什么的......

标签: javascript reactjs react-redux


【解决方案1】:

很高兴您意识到onPaste 是错误的。这是解决方案的一半。 :)

您仍然缺少的部分是 setState 是一个异步函数。每当你调用setState 时,你只是在排队新的状态数据。 react 如此强大的部分原因在于它的内在能力,事实上,采用多个 setState 函数并将它们整合到一个单一的更新中(因此,一个单一的测试是否重新渲染)。

也就是说,setState 函数确实允许回调函数作为辅助参数。使用这些回调来指定应用新状态之后应该做什么。它应该看起来像这样;

this.setState({email: e.target.value }, this.validateEmail)

【讨论】:

  • 没有看到这个我发布的答案可能与您输入的答案相同
  • 加上你的更干净!
  • 另外:我不熟悉 React-Bootstrap,但一般来说,输入应该是动态的或受控的。您正在设置一个 onChange 函数来更新状态以及一个从状态中提取的 value 值。我的猜测是,删除 value 属性也将“解决”重复输入的问题,因为它仍然会设置两次状态,但至少将其设置为相同的值。
  • 好吧,我不再有 dup 问题了,我删除了 onPaste ..看来 onChange -was- 检测到一封电子邮件已粘贴,我认为实际上不是一直以来都是异步 setState() 问题吗
  • 如果您还没有这样做,我建议您从密码 FormControl 中删除 value 属性。我认为这以及您之前的重复问题可能都是由于您的组件受其父级控制并将信息传递给其父级造成的。你真的应该只做其中一个。
【解决方案2】:

是的,这里是 n00b。发现问题:React - State not updated

解决方案:

  handleEmailInput(e) {
    this.setState({email: e.target.value }, () => {
      this.validateEmail()
    })
  }

现在 validateEmail 仅在队列状态转换真正完成更新 this.state.email 时才被调用。

【讨论】:

    【解决方案3】:

    与#3 相关:setState 是异步的,因此不能保证在调用结束时已更新状态。相反,您可以传递将在状态更新时调用的回调:

    this.setState({email: value}, () => { /* State is updated */ });
    

    可能与您的问题无关,但您在密码输入字段中使用了“handleEmailPaste”。

    另外,多次调用 setState 将永远不会附加文本,因此即使使用相同的值调用两次,它也不会重复该值。在这种情况下,从 FormControl 传递的值更有可能是错误的。我会在“输入”和“粘贴”回调中添加断点/console.log,以检查从 FormControl 传递的值。

    最后(也与您的问题无关),在 #3 中,您的密码事件不应是“onEvent={() => this.props.handleXyz}”。那不会调用回调。相反,它应该是 onEvent={this.props.handleXyz}。

    【讨论】: