【问题标题】:React JS Higher-Order-Component not execute function if state is being set outside that function如果在该函数之外设置状态,则 React JS 高阶组件不执行函数
【发布时间】:2019-09-04 01:06:02
【问题描述】:

我想不出一个适合问题标题的句子,所以如果标题令人困惑,首先我想道歉。

所以我刚刚完成了 React JS 的学习并尝试构建一个应用程序进行练习。在这个应用程序中有一个登录表单,如果输入的电子邮件或密码错误,它会显示一个警告并空白字段。

使用基本的 React JS 实现一切都很好,但是当我尝试修改我的代码以实现高阶组件时,应用程序变得有问题,我似乎无法找到罪魁祸首。

所以问题是,如果我输入的电子邮件或密码不正确,然后单击登录,那么这些字段将被空白让我输入/重试,但似乎 字段不会接受任何输入,它只是保持空白无论我在键盘上按什么键,所以我需要刷新页面以便字段可以再次接受我的输入。

这是我的代码:

function ResponseAlert(props){
    if(props.alertStatus!==undefined && props.alertStatus!==null){
        const className = 'alert alert-' + props.alertStatus;
        return <div className={className}>{props.alertMessage}</div>
    }

    return <div></div>;
}
function H2Title(props) {
    return <h2>{props.title}</h2>;
}
class InputText extends React.Component{
    constructor(props){
        super(props);
        this.handleChange = this.handleChange.bind(this);
    }
    handleChange(evt){
        this.props.onInputChange(evt.target.id, evt.target.value);
    }
    render(){
        return (
                <div className="form-group">
                    <label htmlFor={this.props.elemId}>{this.props.label}</label>
                    <input type={this.props.type} className="form-control" name={this.props.elemId} id={this.props.elemId} value={this.props.elemValue} onChange={this.handleChange} required={this.props.required} />
                </div>
            );
    }
}

function withSetStateFormData(WrappedComponents, componentName){
    return class extends React.Component{
        constructor(props){
            super(props);
            this.state = { formdata: { email: '', password: '' } };
            this.setStateFormData = this.setStateFormData.bind(this);
        }
        setStateFormData(field, value){
            this.setState(function(state, props){
                let tempData = state.formdata;
                tempData[field] = value;
                return {
                    formdata: tempData
                };
            });
        }
        render(){
            return <WrappedComponents handleInputChange={this.setStateFormData} {...this.props} {...this.state} />
        }
    }
}

class FormLogin extends React.Component {
            constructor(props){
                super(props);
                this.state = {
                    alertstatus: null,
                    alertmessage: '',
                    formdata: this.props.formdata
                };
                this.handleSubmit = this.handleSubmit.bind(this);
            }  
            handleSubmit(evt){
                evt.preventDefault();

                const self = this;
                fetch("http://someurl.com/login.php",
                    {
                       mode: 'cors',
                       method: 'POST',
                       headers: {
                          'Accept': 'application/json',
                          'Content-Type': 'application/json'
                       },
                       body: JSON.stringify(this.state.formdata)
                    })
                    .then(function (response) {
                        if (response.status === 200) {
                            response.json().then(function (resData) {
                                alert('Yeay! Successful login.');
                            });
                        }
                        else{
                            //here is where the problem lies, I think.
                            self.setState({
                                alertstatus: 'danger',
                                alertmessage: 'Login failed. Email or password is incorrect.',
                                formdata: { email: '', password: '' }
                            });
                        }
                    })
                    .catch(function (err) {
                        console.log(err);
                    });

                return false;
            }
            render(){
                const handleInputChange = this.props.handleInputChange;
                return (
                    <div>
                        <ResponseAlert alertStatus={this.state.alertstatus} alertMessage={this.state.alertmessage} />
                        <H2Title title="Login"/>
                        <form onSubmit={this.handleSubmit}>
                            <InputText type="email" elemId="email" label="Email Address" elemValue={this.state.formdata.email} onInputChange={handleInputChange} required={true}/>

                            <InputText type="password" elemId="password" label="Password" elemValue={this.state.formdata.password} onInputChange={handleInputChange} required={true}/>

                            <button type='submit' className='btn btn-primary'>Login</button>
                        </form>
                    </div>
                  );
                }
}

const EnhancedComponent = withSetStateFormData(FormLogin, "login");
ReactDOM.render(<EnhancedComponent />, document.querySelector('#app'));

所以我认为在执行 setState 函数以清空电子邮件和密码后,onChange 事件以某种方式拒绝更新输入更改时的字段。

任何人请告诉我是什么导致了这种行为?

【问题讨论】:

    标签: javascript reactjs setstate higher-order-components


    【解决方案1】:

    我只想说你不需要在这里使用更高阶的组件,但是你想要做的事情可以工作。

    我发现有几个问题。

    首先,您根据FormLogin 状态设置InputText value。不过这很好,当InputText 更改时,它会调用onInputChange,后者从高阶组件设置为setStateFormData。问题是setStateFormData 设置了高阶组件的状态,而不是FormLogin 的状态。所以结果是FormLogin 状态不会更新,这意味着InputTextvalue 不会更新。

    我知道您在想什么,“但是在我点击提交之前输入确实会更新”。是的,也不是。

    这给我们带来了与 JS 引用有关的第二个问题。

    来自 React 文档

    state 是对应用更改时组件状态的引用。它不应该直接突变。相反,应该通过基于来自stateprops 的输入构建一个新对象来表示更改。

    如果我们查看setStateFormData,从技术上讲,您通过首先用let tempData = state.formdata; 引用state 内的对象然后用tempData[field] = value; 修改引用的对象来直接改变state。这在 React 中是一个很大的禁忌,因为它会导致各种令人困惑的错误,比如这个。

    您可以从旧状态构建新状态,但您只能复制值,不能复制引用。我们可以通过重写setStateFormData 来解决这个问题:

    setStateFormData(field, value){
      this.setState(function(state, props){
        let newFormData = {
          email: state.formdata.email;
          password: state.formdate.password;
        };
    
        newFormData[field] = value;
    
        return {
          formdata: newFormData
        };
      });
    }
    

    现在,我们正在从旧状态深度复制formdata。太好了,一个错误。如果我们再次测试应用程序,现在我们看到输入在提交之前或之后从未更新。这是因为上面提到的第一个问题。

    之前,InputText value 似乎正在更新,因为 FormLogin 状态似乎正在更新,但是,FormLogin 中的 state.formdata 只是高阶组件中对 state.formdata 的引用在FormLogin 的构造函数中设置为formdata: this.props.formdata。这意味着当setStateFormData直接改变高阶组件的状态时,它也直接改变了FormLogin的状态。这使它看起来一切正常,但实际上只是因为引用。一旦引用丢失,应用程序就会崩溃。那么,我们什么时候失去了参考?如你所料,当我们在handleSubmit 中调用setState 时。

    self.setState({
      alertstatus: 'danger',
      alertmessage: 'Login failed. Email or password is incorrect.',
      formdata: { email: '', password: '' }
    });
    

    这会将FormLogin 中的state.formdata 分配给一个新对象,从而破坏了高阶组件中对state.formdata 的引用。

    好的,那么如何解决它。有几种方法。

    我建议将formdataFormLogin 的状态中完全删除。这样您就不必担心保持两个 formdata 对象同步。只需在高阶组件上创建一个clearFormData 函数并将其传递给FormLogin 以调用handleSubmit。另外需要根据FormLogin的props设置TextInputvalue。我认为这是最干净的解决方案,同时仍然使用高阶组件。

    最简单的解决方案当然是摆脱高阶组件,但如果您的目标是专门了解高阶组件,我不确定这是否是一种选择。

    最后,您可以在FormLogin 上实现getDerivedStateFromProps 函数,该函数将在每次其道具更改时重建FormLogin 状态,而每次高阶状态更改时都会发生巧合。这是一种干净的方式来更新FormLogin 中的formdata,只要它在高阶组件中发生更改。问题是您仍然需要担心每次在 FormLogin 中更改时更新高阶组件中的 formdata

    我希望这会有所帮助。

    【讨论】:

    • 哇一个非常好的和全面的解释。您的回答不仅解决了我的问题,还让我对 react 的行为方式有了更多的了解。是的,正如您猜想的那样,您不使用 HOC 的第二个解决方案不是一个选项,因为它违背了实践的目的,此外,我还将将 setStateFormData 用于 FormSignUp、FormUpdateAccount 等其他组件。所以我需要这个 HOC。我更喜欢您的第一个解决方案,因为对于像我这样的学习初学者来说,它更简单,更容易理解,当然我已经尝试过第一个解决方案,它就像一个魅力。非常感谢。
    猜你喜欢
    • 2021-07-05
    • 2019-12-15
    • 1970-01-01
    • 2018-10-09
    • 2019-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-10
    相关资源
    最近更新 更多