sadtroll
sadtroll

Reputation: 191

Input loses focus when typing

I am having a problem using inputs... I've got two inputs: one has autofocus and the other doesn't. However, when I type in the second input, it loses focus andthe focus returns to the first input.

I've read that React rerenders my component when I type something. I tried putting a key prop and etc, but nothing worked.

In my form (a component called Signup), I have the following:

import React from 'react'
import Input from '../../components/Input'
import styles from './styles.scss'

class Signup extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      name: '',
      email: '',
    }
  }

  onSignup (e, userData) {
    e.preventDefault()
    this.props.onSignup(userData)
  }

  render () {
    return (
      <main className={styles.wrapper}>
        <div className={styles.formSide}>
          <h1>SIGNUP</h1>

          <Input
            id="name"
            label="Name"
            onChange={e => this.setState({ name: e.target.value })}
            autofocus={true}
          />
          <Input
            id="email"
            label="E-mail"
            onChange={e => this.setState({ email: e.target.value })}
          />
        </div>
      </main>
    )
  }
}

Signup.propTypes = {
  onSignup: React.PropTypes.func.isRequired
}

export default Signup

My component Input has this code:

import React, { PropTypes } from 'react'
import MaskedInput from 'react-maskedinput'
import styles from './styles.scss'

function Input (props) {
  let iconComp

  if (props.icon) {
    iconComp = (<img src={props.icon} alt="Icon" />)
  }

  let input = ''

  if (props.type === 'date') {
    input = (
      <MaskedInput
        ref={inp => inp && props.autofocus && inp.focus()}
        onChange={props.onChange}
        mask="11/11/1111"
        placeholder={props.placeholder}
        className={styles.input}
      />
    )
  } else {
    input = (
      <input
        ref={inp => inp && props.autofocus && inp.focus()}
        onChange={props.onChange}
        id={props.id}
        placeholder={props.placeholder}
        type={props.type}
        className={styles.input}
      />
    )
  }

  return (
    <div className={styles.wrapper}>
      <label htmlFor={props.id} className={styles.label}>{props.label}</label>
      <br />
      {input}
      {props.error &&
        <span className={styles.error}>
          {props.errorMessage}
        </span>
      }
      {iconComp}
    </div>
  )
}

Input.propTypes = {
  id: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  icon: PropTypes.string,
  placeholder: PropTypes.string,
  type: PropTypes.string,
  autofocus: PropTypes.bool,
  onChange: PropTypes.func.isRequired,
  error: PropTypes.bool,
  errorMessage: PropTypes.string
}

Input.defaultProps = {
  icon: '',
  placeholder: '',
  type: 'text',
  autofocus: false,
  error: false,
  errorMessage: ''
}

export default Input

How can I solve this problem?

Upvotes: 2

Views: 3780

Answers (2)

Tharaka Wijebandara
Tharaka Wijebandara

Reputation: 8065

The problem is each time you render Input, new line arrow function gets created for ref and called. So it execute inp.focus() each time. One way to avoid this is using class component and define ref callback method as a class function.

class Input extends React.Component {

  refCallback(inp){
    if(this.props.autofocus) inp.focus();
  }

  render(){
    let input = ''

    if (this.props.type === 'date') {
      input = (
        <MaskedInput
          ref={this.refCallback}
          onChange={this.props.onChange}
          mask="11/11/1111"
          placeholder={this.props.placeholder}
        />
      )
    } else {
      input = (
        <input
          ref={this.refCallback}
          onChange={this.props.onChange}
          id={this.props.id}
          placeholder={this.props.placeholder}
          type={this.props.type}
        />
      )
    }

    return (
      <div>
        <label htmlFor={this.props.id}>{this.props.label}</label>
        <br />
        {input}
      </div>
    )
  }
}

export default Input

Updated codepen: http://codepen.io/anon/pen/jmqxWy

(My previous code had some issues since I couldn't test it. But now I have updated the code and it works)

Upvotes: 0

floor
floor

Reputation: 1572

so a simple solution is to enhance your SignUp component to have another property called nameAutoFocus and initialize it to true. Use this property to set the autofocus boolean value. Then add the method componentDidMount and inside set nameAutoFocus to false.

    class Signup extends React.Component {
      constructor (props) {
        super(props)
        this.state = {
          name: '',
          email: '',
        }

        this.nameAutoFocus = true; //new
      }

      onSignup (e, userData) {
        e.preventDefault()
        this.props.onSignup(userData)
      }

       //new
      componentDidMount() {
        this.nameAutoFocus = false;
      }

      render () {
        return (
          <main>
            <div>
              <h1>SIGNUP</h1>

              <Input
                id="name"
                label="Name"
                onChange={e => this.setState({ name: e.target.value })}
                autofocus={this.nameAutoFocus}
              />
              <Input
                id="email"
                label="E-mail"
                onChange={e => this.setState({ email: e.target.value })}
              />
            </div>
          </main>
        )
      }
    }

This works because the initial value of nameAutoFocus is passed to the input giving it focus then componentDidMount will run setting it to false so the next time state changes it won't set the autofocus property to true. This essentially is giving it focus only once when initially rendered.

codepen: http://codepen.io/floor_/pen/PmNRKV?editors=0011 don't forget to click run.

Upvotes: 2

Related Questions