在React Component

时间:2017-06-15 03:46:16

标签: javascript reactjs react-redux

我无法弄清楚为什么在粘贴我的电子邮件后,它会在文本框中复制它。

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

LoginContainer

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

LoginForm的

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,其中setsState为电子邮件,所以如果是这样,(我知道handleEmailInput是也将由粘贴/更改触发),如果handleEmailPaste已经设置它,我不想再次设置状态。相反,如果用户键入了值而不是粘贴它,则此if语句将为bypassed,因此为handleEmailInput would setState in that case

但是我对电子邮件输入的整个处理对我来说都很烦人。如果您认为这是一个黑客并且更好地重构这些代码,请告诉我。

更新#2

废话,我注意到我没有复制但现在我无法在电子邮箱中输入新值,它不会让我和它只是坐在那里填充电子邮件但是我无法修改它。

更新#3 (Rant:为什么没有StackOverflow使得post body textarea的高度更大,我滚动时几乎看不到我在这里做的事情)

所以我的问题确实不再是重复粘贴文本的问题了。我回到原点,我原来的问题是我能够粘贴一些东西,但它使输入无效,我无法弄清楚原因。我想我应该更新这篇文章的标题,但很好。

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

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

  1. 当您第一次粘贴时,它会点击我的handleEmailInput()方法。
  2. handleEmailInput拨打setState({email: e.target.value}),因此您认为现在为此设置.state.email
  3. 但当handleEmailInputvalidateEmail()之后立即调用validateEmail时,会检查this.state.email并且仍然 {{1} } 由于某些原因。因此,最终命中第二个setState,将其设置为无效
  4. 没有第一次致电""设置this.setState({email: e.target.value })到粘贴的电子邮件?我知道当我在该行上设置断点时,this.state.email 确实会收到我粘贴的电子邮件,但在 e.target.value完成后调用,出于某种原因,在validateEmail()中,对于this.state.email,它仍然获得this.setState({email: e.target.value }),我不明白为什么。也许它是React及其生命周期的基础?或其他一些基本的我还没有意识到......不确定。

    LoginContainer (我已经完全删除了onPaste逻辑)

    ""

    LoginForm的 (注意我们只使用了onChange来发送电子邮件)

    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'
    

3 个答案:

答案 0 :(得分:1)

你意识到onPaste是错误的,这很好。这是解决方案的一半。 :)

你还缺少的是setState是一个异步函数。无论何时调用setState,您只需将新状态数据排队。实际上,反应如此强大的部分原因在于它具有多种setState函数并将它们合并为单一更新(因此,是否重新渲染的单一测试)的固有能力。

也就是说,setState函数 允许回调函数作为辅助参数。使用这些回调来指定应用新状态后应该执行的操作。看起来应该是这样的;

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

答案 1 :(得分:0)

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

解决方案:

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

现在只有在排队状态转换真正完成更新this.state.email时才会调用validateEmail。

答案 2 :(得分:0)

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

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

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

此外,多次调用setState将永远不会附加文本,因此即使使用相同的值调用两次,也不会复制该值。更有可能的是,在这种情况下,从FormControl传递的值是错误的。我会在“input”和“paste”回调中添加一个breakpoint / console.log来检查从FormControl传递的值。

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