React JS Higher-Order-Component 如果在该函数之外设置状态则不执行该函数
React JS Higher-Order-Component not execute function if state is being set outside that function
我想不出问题标题的合适句子,所以如果标题有点令人困惑,首先我想道歉。
所以我刚刚学习完 React JS 并尝试构建一个应用程序进行练习。在此应用程序中有一个登录表单,如果输入的电子邮件或密码错误,它将显示警报并将字段留空。
使用基本的 React JS 实现一切都很好,但是当我尝试修改我的代码来实现时 higher-order-component,应用程序出现错误,我似乎无法找到问题所在。
所以有问题的是,如果我输入的电子邮件或密码不正确,然后单击登录,那么输入/重试的字段将被清空,但是似乎 字段不接受任何输入 ,它只是 保持空白 无论我在键盘上按什么键,所以我需要刷新页面这些字段可以再次接受我的输入。
这是我的代码:
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 事件以某种方式拒绝在输入更改时更新字段。
任何人请告诉我是什么导致了这种行为?
我只是想说,你不需要在这里使用更高阶的组件,但是,你尝试做的是可以工作的。
我发现有几个问题。
首先,您根据 FormLogin
状态设置 InputText
value
。但是,这很好,当 InputText
更改时,它会调用 onInputChange
,后者从高阶组件设置为 setStateFormData
。问题是 setStateFormData
设置高阶组件的状态,而不是 FormLogin
的状态。所以结果是 FormLogin
状态没有更新,这意味着 InputText
的 value
没有更新。
我知道你在想什么,"But the input does update before I click submit"。是的,也不是。
这给我们带来了第二个问题,它与 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
,方法是首先使用 let tempData = state.formdata;
引用 state
中的对象,然后使用 [=30] 修改引用的对象=].这是 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
只是一个在 FormLogin
的构造函数中使用 formdata: this.props.formdata
设置的高阶组件中对 state.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
的引用。
好的,那么如何解决呢。有几种方法。
我建议将 formdata
从 FormLogin
的状态中完全移除。这样您就不必担心保持两个 formdata
对象同步。只需在高阶组件上创建一个 clearFormData
函数并将其传递给 FormLogin
以调用 handleSubmit
。另外,需要根据FormLogin
的道具设置TextInput
value
。我认为这是最干净的解决方案,同时仍然使用高阶组件。
最简单的解决方案当然是摆脱高阶组件,但如果您的目标是专门了解高阶组件,我不确定这是否是一个选项。
最后,您可以在 FormLogin
上实现一个 getDerivedStateFromProps 函数,它会在每次其 props 更改时重建 FormLogin
状态,这恰好发生在每次高阶状态更改时。这是更新 FormLogin
中的 formdata
的一种干净的方法,每当它在高阶组件中发生变化时。问题是你仍然需要担心每次 FormLogin
.
变化时更新高阶组件中的 formdata
希望对您有所帮助。
我想不出问题标题的合适句子,所以如果标题有点令人困惑,首先我想道歉。
所以我刚刚学习完 React JS 并尝试构建一个应用程序进行练习。在此应用程序中有一个登录表单,如果输入的电子邮件或密码错误,它将显示警报并将字段留空。
使用基本的 React JS 实现一切都很好,但是当我尝试修改我的代码来实现时 higher-order-component,应用程序出现错误,我似乎无法找到问题所在。
所以有问题的是,如果我输入的电子邮件或密码不正确,然后单击登录,那么输入/重试的字段将被清空,但是似乎 字段不接受任何输入 ,它只是 保持空白 无论我在键盘上按什么键,所以我需要刷新页面这些字段可以再次接受我的输入。
这是我的代码:
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 事件以某种方式拒绝在输入更改时更新字段。
任何人请告诉我是什么导致了这种行为?
我只是想说,你不需要在这里使用更高阶的组件,但是,你尝试做的是可以工作的。
我发现有几个问题。
首先,您根据 FormLogin
状态设置 InputText
value
。但是,这很好,当 InputText
更改时,它会调用 onInputChange
,后者从高阶组件设置为 setStateFormData
。问题是 setStateFormData
设置高阶组件的状态,而不是 FormLogin
的状态。所以结果是 FormLogin
状态没有更新,这意味着 InputText
的 value
没有更新。
我知道你在想什么,"But the input does update before I click submit"。是的,也不是。
这给我们带来了第二个问题,它与 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 fromstate
andprops
.
如果我们查看 setStateFormData
,您在技术上直接改变 state
,方法是首先使用 let tempData = state.formdata;
引用 state
中的对象,然后使用 [=30] 修改引用的对象=].这是 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
只是一个在 FormLogin
的构造函数中使用 formdata: this.props.formdata
设置的高阶组件中对 state.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
的引用。
好的,那么如何解决呢。有几种方法。
我建议将 formdata
从 FormLogin
的状态中完全移除。这样您就不必担心保持两个 formdata
对象同步。只需在高阶组件上创建一个 clearFormData
函数并将其传递给 FormLogin
以调用 handleSubmit
。另外,需要根据FormLogin
的道具设置TextInput
value
。我认为这是最干净的解决方案,同时仍然使用高阶组件。
最简单的解决方案当然是摆脱高阶组件,但如果您的目标是专门了解高阶组件,我不确定这是否是一个选项。
最后,您可以在 FormLogin
上实现一个 getDerivedStateFromProps 函数,它会在每次其 props 更改时重建 FormLogin
状态,这恰好发生在每次高阶状态更改时。这是更新 FormLogin
中的 formdata
的一种干净的方法,每当它在高阶组件中发生变化时。问题是你仍然需要担心每次 FormLogin
.
formdata
希望对您有所帮助。