首页 > 解决方案 > 如果在该函数之外设置状态,则 React JS 高阶组件不执行函数

问题描述

我想不出问题标题的正确句子,所以首先我想道歉,如果标题有点令人困惑。

所以我刚刚完成了 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 事件以某种方式拒绝更新输入更改的字段。

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

标签: javascriptreactjssetstatehigher-order-components

解决方案


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

我看到了几个问题。

首先,您正在InputText value根据FormLogin状态进行设置。没关系,但是,当InputText更改时,它会调用从高阶组件onInputChange设置为的。setStateFormData问题是它setStateFormData设置了高阶组件的状态,而不是FormLogin. 所以结果是FormLogin状态没有更新,这意味着valueforInputText没有更新。

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

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

来自 React 文档

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

如果我们看到setStateFormData您在技术上是state通过首先在statewith中引用一个对象let tempData = state.formdata;然后用 修改引用的对象来直接改变tempData[field] = value;。这在 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状态似乎正在更新,所以 in 似乎正在更新,但state.formdatainFormLogin只是在withstate.formdata的构造函数中设置的高阶组件中的引用。这意味着当直接改变高阶组件的状态时,它也直接改变了 的状态。这使它看起来一切正常,但实际上只是因为引用。一旦引用丢失,应用程序就会崩溃。那么,我们什么时候失去了参考?如您所料,当我们调用.FormLoginformdata: this.props.formdatasetStateFormDataFormLoginsetStatehandleSubmit

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

这分配给一个新对象,打破了高阶组件中state.formdata的引用。FormLoginstate.formdata

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

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

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

最后,您可以实现一个getDerivedStateFromProps函数,每次它的 props 更改时都会在FormLogin该函数上重建FormLogin状态,而这种情况恰好在每次高阶状态更改时发生。formdata这是一种在FormLogin高阶组件中发生变化时更新所有内容的干净方法。问题是您仍然需要担心formdata每次更改高阶组件时都会更新FormLogin.

我希望这有帮助。


推荐阅读