小能豆

React - 检查元素在 DOM 中是否可见

javascript

我正在构建一个表单 - 用户需要回答一系列问题(单选按钮),然后才能进入下一个屏幕。对于字段验证,我使用 yup(npm 包)和 redux 作为状态管理。

对于一个特定的场景/组合,会显示一个新屏幕 (div),要求用户确认 (复选框),然后才能继续。我希望仅在显示时才应用此复选框的验证。

如何使用 React 检查元素(div)是否显示在 DOM 中?

我想到的方法是将变量“isScreenVisible”设置为 false,如果满足条件,我会将状态更改为“true”。

我正在执行检查并在 _renderScreen() 中将“isScreenVisible”设置为 true 或 false,但由于某种原因,它进入了无限循环。

我的代码:

class Component extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      formisValid: true,
      errors: {},
      isScreenVisible: false
    }

    this.FormValidator = new Validate();
    this.FormValidator.setValidationSchema(this.getValidationSchema());
  }

  areThereErrors(errors) {
    var key, er = false;
    for(key in errors) {
      if(errors[key]) {er = true}
    }
    return er;
  }

  getValidationSchema() {
    return yup.object().shape({
      TravelInsurance: yup.string().min(1).required("Please select an option"),
      MobilePhoneInsurance: yup.string().min(1).required("Please select an option"),
      Confirmation: yup.string().min(1).required("Please confirm"),
    });
  }

  //values of form fields
  getValidationObject() {
    let openConfirmation = (this.props.store.Confirmation === true)? 'confirmed': ''

    return {
      TravelInsurance: this.props.store.TravelInsurance,
      MobilePhoneInsurance: this.props.store.MobilePhoneInsurance,
      Confirmation: openConfirmation,
    }
  }

  setSubmitErrors(errors) {
    this.setState({errors: errors});
  }

  submitForm() {
    var isErrored, prom, scope = this, obj = this.getValidationObject();
    prom = this.FormValidator.validateSubmit(obj);

    prom.then((errors) => {
      isErrored = this.FormValidator.isFormErrored();

      scope.setState({errors: errors}, () => {
        if (isErrored) {
        } else {
          this.context.router.push('/Confirm');
        }
      });
    });
  }

  saveData(e) {
    let data = {}
    data[e.target.name] = e.target.value

    this.props.addData(data)

    this.props.addData({
      Confirmation: e.target.checked
    })
  }

  _renderScreen = () => {
    const {
      Confirmation
    } = this.props.store

    if(typeof(this.props.store.TravelInsurance) !== 'undefined' && typeof(this.props.store.MobilePhoneInsurance) !== 'undefined') &&
    ((this.props.store.TravelInsurance === 'Yes' && this.props.store.MobilePhoneInsurance === 'No') ||
    (this.props.store.TravelInsurance === 'No' && this.props.store.MobilePhoneInsurance === 'Yes')){

        this.setState({
            isScreenVisible: true
        })

          return(
            <div>
                <p>Please confirm that you want to proceed</p>

                  <CheckboxField
                    id="Confirmation"
                    name="Confirmation"
                    value={Confirmation}
                    validationMessage={this.state.errors.Confirmation}
                    label="I confirm that I would like to continue"
                    defaultChecked={!!Confirmation}
                    onClick={(e)=> {this.saveData(e)} }
                  />
                </FormLabel>
            </div>
          )
      }
      else{
        this.setState({
            isScreenVisible: false
        })
      }
  }

  render(){
    const {
      TravelInsurance,
      MobilePhoneInsurance
    } = this.props.store

    return (
      <div>           
          <RadioButtonGroup
            id="TravelInsurance"
            name="TravelInsurance"
            checked={TravelInsurance}
            onClick={this.saveData.bind(this)}
            options={{
              'Yes': 'Yes',
              'No': 'No'
            }}
            validationMessage={(this.state.errors.TravelInsurance) ? this.state.errors.TravelInsurance : null }
          />

        <RadioButtonGroup
          id="MobilePhoneInsurance"
          name="MobilePhoneInsurance"
          checked={MobilePhoneInsurance}
          onClick={this.saveData.bind(this)}
          options={{
            'Yes': 'Yes',
            'No': 'No'
          }}
          validationMessage={(this.state.errors.MobilePhoneInsurance) ? this.state.errors.MobilePhoneInsurance : null }
        />

        this._renderScreen()

        <ButtonRow
            primaryProps={{
                children: 'Continue',
                onClick: e=>{
                this.submitForm();
            }
        }}
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    store: state.Insurance,
  }
}

const Insurance = connect(mapStateToProps,{addData})(Component)

export default Insurance

阅读 37

收藏
2024-06-06

共1个答案

小能豆

为了根据状态有条件地渲染组件并避免无限循环,您不应在方法setState内部调用render或在渲染期间调用的任何方法。相反,您应该在渲染组件之前计算状态值。

isScreenVisible在您的例子中,您在方法内部设置状态_renderScreen,该方法在渲染期间调用。这会导致无限循环,因为设置状态会触发重新渲染,_renderScreen再次调用会设置状态,从而形成循环。

要解决此问题,您应该isScreenVisible在渲染组件之前计算该值,并在必要时更新它。以下是重构代码的方法:

class Component extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      formisValid: true,
      errors: {},
      isScreenVisible: false
    }

    this.FormValidator = new Validate();
    this.FormValidator.setValidationSchema(this.getValidationSchema());
  }

  componentDidMount() {
    this.updateScreenVisibility();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.store !== this.props.store) {
      this.updateScreenVisibility();
    }
  }

  updateScreenVisibility() {
    const { TravelInsurance, MobilePhoneInsurance } = this.props.store;
    const isVisible = (
      typeof TravelInsurance !== 'undefined' &&
      typeof MobilePhoneInsurance !== 'undefined' &&
      ((TravelInsurance === 'Yes' && MobilePhoneInsurance === 'No') ||
      (TravelInsurance === 'No' && MobilePhoneInsurance === 'Yes'))
    );

    this.setState({ isScreenVisible: isVisible });
  }

  // other methods...

  render() {
    const { TravelInsurance, MobilePhoneInsurance } = this.props.store;
    const { errors, isScreenVisible } = this.state;

    return (
      <div>
        <RadioButtonGroup
          id="TravelInsurance"
          name="TravelInsurance"
          checked={TravelInsurance}
          onClick={this.saveData.bind(this)}
          options={{
            'Yes': 'Yes',
            'No': 'No'
          }}
          validationMessage={(errors.TravelInsurance) ? errors.TravelInsurance : null}
        />

        <RadioButtonGroup
          id="MobilePhoneInsurance"
          name="MobilePhoneInsurance"
          checked={MobilePhoneInsurance}
          onClick={this.saveData.bind(this)}
          options={{
            'Yes': 'Yes',
            'No': 'No'
          }}
          validationMessage={(errors.MobilePhoneInsurance) ? errors.MobilePhoneInsurance : null}
        />

        {isScreenVisible && this.renderConfirmationScreen()}

        <ButtonRow
          primaryProps={{
            children: 'Continue',
            onClick: this.submitForm
          }}
        />
      </div>
    )
  }

  renderConfirmationScreen() {
    const { Confirmation } = this.props.store;
    const { errors } = this.state;

    return (
      <div>
        <p>Please confirm that you want to proceed</p>
        <CheckboxField
          id="Confirmation"
          name="Confirmation"
          value={Confirmation}
          validationMessage={errors.Confirmation}
          label="I confirm that I would like to continue"
          defaultChecked={!!Confirmation}
          onClick={this.saveData}
        />
      </div>
    );
  }
}

// other code...

export default Insurance;

在此重构代码中:

  • 该方法在生命周期钩子updateScreenVisibility中被调用,根据您提供的条件来计算状态。componentDidMount``componentDidUpdate``isScreenVisible
  • 确认屏幕的条件渲染现在直接在render基于isScreenVisible状态的方法中完成。该renderConfirmationScreen方法用于渲染确认屏幕。
  • saveData方法直接传递给onClick的 prop CheckboxField,无需内联箭头函数。
2024-06-06