javascript - 如果在该函数之外设置状态,则 React JS 高阶组件不执行函数

标签 javascript reactjs setstate higher-order-components

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

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

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

最佳答案

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

我看到了几个问题。

首先,您正在设置 InputText value根据FormLogin状态。不过没关系,当 InputText更改,它调用 onInputChange设置为 setStateFormData来自高阶组件。问题是 setStateFormData设置高阶组件的状态,而不是 FormLogin 的状态.所以结果是FormLogin状态不更新,这意味着 value对于 InputText不更新。

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

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

来自 React 文档

state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.



如果我们看一下 setStateFormData你在技术上直接变异state通过首先引用 state 中的对象与 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然而,状态似乎正在更新,state.formdataFormLogin只是对 state.formdata 的引用在 FormLogin 的构造函数中设置的高阶组件中与 formdata: this.props.formdata .这意味着当 setStateFormData直接改变了高阶组件的状态,也直接改变了FormLogin的状态.这使它看起来一切正常,但实际上只是因为引用。一旦引用丢失,应用程序就会崩溃。那么,我们什么时候失去了引用?如您所料,当我们调用 setStatehandleSubmit .
self.setState({
  alertstatus: 'danger',
  alertmessage: 'Login failed. Email or password is incorrect.',
  formdata: { email: '', password: '' }
});

此分配 state.formdataFormLogin到一个新对象,打破对 state.formdata 的引用在高阶组件中。

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

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

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

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

我希望这有帮助。

关于javascript - 如果在该函数之外设置状态,则 React JS 高阶组件不执行函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55665638/

相关文章:

html - React/Flexbox/SPA/无滚动条 : Trouble segmenting page into header/content/footer that takes up the whole page

node.js - 通过重定向将数据从 Express 发送到 React

javascript - 遍历元素数组,用数据替换html

javascript - Backbone.js 中的可选路由参数? (再次)

javascript - 在 lit-element 中重用 firebase 常量

ReactJS 并发 SetState 竞争条件

javascript - 在 React Js 中使用多个复选框过滤列表

javascript - jQuery:函数如何仅通过点 (.) 工作

javascript - 渲染CSS样式应用了React无状态功能语句

javascript - React Native 和 Firebase promise - 对渲染产生奇怪的副作用