我正在构建一个表单 - 用户需要回答一系列问题(单选按钮),然后才能进入下一个屏幕。对于字段验证,我使用 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
为了根据状态有条件地渲染组件并避免无限循环,您不应在方法setState内部调用render或在渲染期间调用的任何方法。相反,您应该在渲染组件之前计算状态值。
setState
render
isScreenVisible在您的例子中,您在方法内部设置状态_renderScreen,该方法在渲染期间调用。这会导致无限循环,因为设置状态会触发重新渲染,_renderScreen再次调用会设置状态,从而形成循环。
isScreenVisible
_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
renderConfirmationScreen
saveData
onClick
CheckboxField