Reactでformat付きの数値入力コンポーネントを作る

こういう機能がついた入力フォーム、どのようなサービスでも役立つと思う。 ぐぐってもあんまり情報が見つからなかったので自分でまとめる

  • フォーカスインしているときは普通の入力エリア
  • フォーカスアウトしたら、三桁区切りでフォーマット
  • 指定した範囲内の値しか入力不可
  • onChangeイベントで数値データを渡す

この実装で注意しないといけないのが、 -.
-16.0であれば数値としても妥当であるが、Reactでは(というかJavaScriptでは) -.が入力された瞬間を考慮しなければならない

で、作ったのが↓こんなの(うろ覚え)


class InputNumber extends Component {
  constructor(props) {
    super(props)
    this.state = {
      isOnFocus: false,
      value: '',
    };
  }

  get value() {
    if (this.state.isOnFocus) {
      return this.state.value || ''
    }
    if (this.props.value) {
      return this.props.format(+value);
    }
    return '0'
  }

  includes = value => this.props.min <= value && value <= this.props.max;

  handleFocus = () => {
    this.setState({
      isOnFocus: true,
      value: `${this.props.value}`
    });
  };

  handleBlur = () => {
    this.setState({
      isOnFocus: false,
    });
  }

  handleChange = (e) => {
    const { value } = e.target;
    if (this.isIncludes(value)) {
      this.props.onChange(+value);
    }
    // ここがどうにも苦しいロジック
    if (this.isIncludes(value) || value === '-') {
      this.setState({
        value,
      })
    }
  }

  render() {
    <input
      type="text"
      className="form-input"
      placeHolder={this.props.placeHolder}
      disabled={this.props.disabled}
      value={this.value}
      onFocus={this.handleFocus}
      onBlur={this.handleBlur}
      onChange={this.handleChange}
    />
  }
}

InputNumber.propTypes = {
  value: PropTypes.number.isRequired,
  min: PropTypes.number,
  max: PropTypes.number,
  placeHolder: PropTypes.string,
  disabled: PropTypes.bool,
  onChange: PropTypes.func.isRequired,
  format: PropTypes.func
}

InputNumber.defaultProps = {
  min: -Infinity,
  max: Infinity,
  disabled: false,
  placeHolder: '',
  format: formatDefault
}

formatDefault = value => ''; // お好きな数値フォーマッタを実装しよう!

今気づいたけどモバイル端末を考えると htmlのnumber使うようにした方が良いんだろうなぁ