【问题标题】:How to onFocus and onBlur a React/Redux form field that's connected to React Date Picker?如何 onFocus 和 onBlur 连接到 React Date Picker 的 React/Redux 表单字段?
【发布时间】:2017-02-25 03:09:35
【问题描述】:

我有一个简单的 v6 redux-form,它的输入呈现一个自定义组件,该组件使用 react-day-picker 填充其值。

https://github.com/gpbl/react-day-picker

我之所以选择 react-day-picker 是因为它不依赖于 moment.js 并且适用于我当前的设置。

当我聚焦该字段时,我希望日期选择器弹出,但如果我点击任何不是日期选择器的地方,我希望它消失。

基本上,我希望我的 React datepicker 像 jQueryUI 一样工作:

https://jqueryui.com/datepicker/

我最终得出的三种情况是:

  1. 我点击字段,日期选择器弹出,但不会消失,除非我选择日期或再次点击字段(这对我们的需要来说太死板了)。

  2. 我单击该字段,日期选择器会弹出,但如果我单击任何位置,它会很快消失,因为输入字段的 onBlur 在它处理日期选择器的单击事件之前被调用,以使用所选日期填充该字段.

  3. 我单击该字段,日期选择器弹出,自动聚焦,正确模糊,除非我单击任何不是

    或日期选择器的内容。

我首先尝试使用包含整个页面的同级空 div,因此当我单击空 div 时,它会正确切换日期选择器。这适用于 z-indexes 和 position: fixed 直到我更改了 datepicker 的月份,这似乎重新渲染了 datepicker 并弄乱了点击的顺序,这又导致了情况 2)。

我最近的尝试是在日期选择器 div 弹出时自动聚焦它,所以当我模糊任何不是日期选择器的东西时,它会切换日期选择器。这在理论上是有效的,除了 datepicker 是一个内部有许多嵌套

的组件来控制日、周、月、禁用日......所以当我点击“日”时,它会注册一个模糊,因为我m 点击“day”<div>,而不是最初关注的根“datepicker”<div>

上面的解决方案是调整'datepicker'<div>的onBlur,这样它只会在document.activeElement为

时切换日期选择器,但这只有在我不点击另一个表单字段时才有效.

WizardFormPageOne.js:

function WizardFormPageOne({ handleSubmit }) {
  return (
    <form onSubmit={handleSubmit} className="col-xs-6">
      <h1>WizardFormPageOne</h1>

      <div className="card">
        <div className="card-block">
          <div className="form-group">
            <label htmlFor="first">Label 1</label>
            <Field type="text" name="first" component={DateInput} className="form-control" />
          </div>
          ...

export default reduxForm({
  form: 'wizardForm',
  destroyOnUnmount: false,
})(WizardFormPageOne);

DateInput.js:

import React from 'react';

import styles from './styles.css';
import DatePicker from '../DatePicker';

class DateInput extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);

    this.state = {
      dateValue: new Date(),
      activeDateWidget: false,
    };
  }

  changeDate = (date) => {
    this.setState({
      dateValue: date,
    });
  }

  changeActiveDateWidget = (e) => {
    e.stopPropagation();
    this.setState({
      activeDateWidget: !this.state.activeDateWidget,
    });
  }

  render() {
    const { input, meta } = this.props;
    const { dateValue, activeDateWidget } = this.state;
    return (
      <div className={styles.dateInput}>
        <input
          {...input}
          className="form-control"
          type="text"
          value={dateValue}
          onClick={this.changeActiveDateWidget}
          // onBlur={this.changeActiveDateWidget}
        />

        {activeDateWidget ? (
          <div>
            <DatePicker
              changeActiveDateWidget={this.changeActiveDateWidget}
              changeDate={this.changeDate}
              dateValue={dateValue}
            />
          </div>
        ) : (
          <div></div>
        )}
      </div>
    );
  }
}

export default DateInput;

DatePicker.js:

import React from 'react';
import 'react-day-picker/lib/style.css';
import DayPicker, { DateUtils } from 'react-day-picker';

import styles from './styles.css';
import disabledDays from './disabledDays';


class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
  constructor(props) {
    super(props);
    this.state = {
      selectedDay: new Date(),
    };
  }

  componentDidMount() {
    if (this._input) {
      this._input.focus();
    }
  }

  handleDayClick = (e, day, { disabled }) => {
    e.stopPropagation();
    if (disabled) {
      return;
    }
    this.setState({ selectedDay: day }, () => {
      this.props.changeDate(day);
      this.props.changeActiveDateWidget();
    });
  }

  focusThisComponent = (e) => {
    if (e) {
      this._input = e;
    }
  }

  focusIt = () => {
    console.log('focusing');
  }

  blurIt = () => {
    console.log('blurring');
    setTimeout(() => {
      if (document.activeElement == document.body) {
        this.props.changeActiveDateWidget();
      }
    }, 1);
  }

  render() {
    const { changeActiveDateWidget } = this.props;
    const { selectedDay } = this.state;
    return (
      <div
        className={styles.datePicker}
        ref={this.focusThisComponent}
        tabIndex="1"
        onFocus={this.focusIt}
        onBlur={this.blurIt}
      >
        <DayPicker
          id="THISTHING"
          initialMonth={selectedDay}
          disabledDays={disabledDays}
          selectedDays={(day) => DateUtils.isSameDay(selectedDay, day)}
          onDayClick={this.handleDayClick}
        />
      </div>
    );
  }
}

export default DatePicker;

这是我现在遇到的问题的截屏视频:

http://screencast.com/t/kZuIwUzl

日期选择器可以正确切换,除非单击另一个字段,此时它会停止正确模糊/切换。我所有的修修补补要么导致我进入上面列出的三个场景之一。

【问题讨论】:

    标签: reactjs datepicker redux redux-form react-day-picker


    【解决方案1】:

    有点老的问题,但我找不到简单的答案,所以这就是我所做的:

    onBlur={(e) => { 
      var picker = document.querySelector(".DayPicker")
      if(!picker.contains(e.relatedTarget)){
        this.setState({showDayPicker: false}) 
      }
    }}
    

    如果模糊不是来自于点击 DayPicker,我会设置一个隐藏 DayPicker 的标志,否则它会保持 DayPicker 显示

    【讨论】:

      【解决方案2】:

      我无法让 Steffen 的答案适用于我的方案,如果您打开日期选择器,单击日期选择器的非活动部分,然后单击外部日期选择器。在这一点上我可能会吹毛求疵,他的解决方案可能更容易实现,但这是我为解决它所做的:

      在 DatePicker.js 中,设置一个空数组作为有效

      的集合。当 onBlur 被触发时,调用一个递归函数,它以根 DatePicker 为参数,解析它的所有子元素,并将它们添加到空数组中。之后,检查 document.activeElement 以查看它是否在数组中。如果没有,则切换 DatePicker 小部件,否则,什么也不做。

      请注意,对 document.activeElement 的检查必须在模糊后一个刻度进行,否则 activeElement 将是

      相关链接:

      Get the newly focussed element (if any) from the onBlur event.

      Which HTML elements can receive focus?

      /**
      *
      * DatePicker
      *
      */
      
      import React from 'react';
      import 'react-day-picker/lib/style.css';
      import DayPicker, { DateUtils } from 'react-day-picker';
      
      import styles from './styles.css';
      import disabledDays from './disabledDays';
      
      class DatePicker extends React.Component { // eslint-disable-line react/prefer-stateless-function
        constructor(props) {
          super(props);
          this.state = {
            selectedDay: new Date(),
          };
      
          this.validElements = [];
        }
      
        componentDidMount() {
          // Once DatePicker successfully sets a ref, component will mount
          // and autofocus onto DatePicker's wrapper div.
          if (this.refComponent) {
            this.refComponent.focus();
          }
        }
      
        setRefComponent = (e) => {
          if (e) {
            this.refComponent = e;
          }
        }
      
        findDatePickerDOMNodes = (element) => {
          if (element.hasChildNodes()) {
            this.validElements.push(element);
            const children = element.childNodes;
            for (let i = 0; i < children.length; i++) {
              this.validElements.push(children[i]);
              this.findDatePickerDOMNodes(children[i]);
            }
            return;
          }
        }
      
        handleDayClick = (e, day, { disabled }) => {
          if (disabled) {
            return;
          }
          this.setState({ selectedDay: day }, () => {
            this.props.changeDate(day);
            this.props.changeActiveDateWidget();
          });
        }
      
        handleBlur = () => {
          // Since DatePicker's wrapper div has been autofocused on mount, all
          // that needs to be done is to blur on anything that's not the DatePicker.
          // DatePicker has many child divs that handle things like day, week, month...
          // invoke a recursive function to gather all children of root DatePicker div, then run a test against valid DatePicker elements. If test fails, then changeActiveDateWidget.
          setTimeout(() => {
            const rootDatePickerElement = document.getElementById('datePickerWidget');
            this.findDatePickerDOMNodes(rootDatePickerElement);
            if (!this.validElements.includes(document.activeElement)) {
              this.props.changeActiveDateWidget();
            }
          }, 1);
        }
      
        render() {
          const { selectedDay } = this.state;
          return (
            <div
              className={styles.datePicker}
              onBlur={this.handleBlur}
              // tabIndex necessary for element to be auto-focused.
              tabIndex="1"
              ref={this.setRefComponent}
            >
              <DayPicker
                initialMonth={selectedDay}
                disabledDays={disabledDays}
                selectedDays={(day) => DateUtils.isSameDay(selectedDay, day)}
                onDayClick={this.handleDayClick}
                id="datePickerWidget"
              />
            </div>
          );
        }
      }
      
      DatePicker.propTypes = {
        changeDate: React.PropTypes.func,
        changeActiveDateWidget: React.PropTypes.func,
      };
      
      export default DatePicker;
      

      在 DateInput.js 中,单击输入可能会导致切换触发两次,所以我只是将其设置为在单击输入时始终切换为真:

      render() {
          const { input, meta } = this.props;
          const { dateValue, activeDateWidget } = this.state;
          return (
            <div className={styles.dateInput}>
              <input
                {...input}
                className="form-control"
                type="text"
                value={dateValue}
                onClick={() => { this.setState({ activeDateWidget: true }); }}
              />
      

      【讨论】:

      • 您可能想查看其他日期选择器组件。在我看来,焦点打开/关闭应该由组件正确处理。作为一种解决方法,您可以将组件包装在这样的组件中:github.com/nkbt/react-page-click。如果您对其他实现持开放态度,请查看此组件:airbnb.io/react-dates/…
      • react-page-click 看起来很棒,可以为我们省去很多麻烦。 Airbnb datepicker 是我的第一选择,尽管我无法在 create-react-app 上启动并运行一个简单的示例,更不用说找出它是否会与来自 mxstbr/react-boilerplate 的依赖项冲突。
      【解决方案3】:

      你可以拿这个例子http://react-day-picker.js.org/examples/?overlay 做一些小的修改来使它兼容redux-form v6。而不是使用本地状态,您应该在渲染函数中使用 redux-form Field 组件提供的 this.props.input.value。此外,在handleInputChange 事件处理程序中,您必须调用this.props.input.onChange(e.target.value) 而不是this.setState({ value: e.target.value }),并且在handleInputBlur 事件中调用this.props.input.onBlur(e.target.value)。这就是让它作为一个 redux-form Field 组件工作所需要做的一切。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-03-29
        • 2019-07-25
        • 1970-01-01
        • 2017-10-02
        • 2021-03-28
        • 2018-03-23
        • 2019-08-11
        • 1970-01-01
        相关资源
        最近更新 更多