React setState 未能捕获被拒绝的 Promise
Posted
技术标签:
【中文标题】React setState 未能捕获被拒绝的 Promise【英文标题】:React setState fails in catch of rejected Promise 【发布时间】:2019-08-10 16:12:40 【问题描述】:在 Promise 的 catch 中设置状态时出现错误。在下面的示例中,Promise 的 catch
在 onClickSave()
方法中。我相信我得到了错误,因为我误解了我所在的this
上下文。这里我想使用this
来处理DialogUsersNewProps
类的内容。来自 Java,this
的行为有点不同,我过去已经对 javascript 感到困惑。我必须怎么做才能从被拒绝的 Promise 的 catch
中设置状态?
来自浏览器控制台的错误:
/home/myuser/Documents/myprog/administration2/node_modules/react-dom/cjs/react-dom.development.js:506 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
in input (created by InputBase)
in div (created by InputBase)
in InputBase (created by Context.Consumer)
in WithFormControlContext(InputBase) (created by WithStyles(WithFormControlContext(InputBase)))
in WithStyles(WithFormControlContext(InputBase)) (created by Input)
in Input (created by WithStyles(Input))
in WithStyles(Input) (created by TextField)
in div (created by FormControl)
in FormControl (created by WithStyles(FormControl))
in WithStyles(FormControl) (created by TextField)
in TextField (created by DialogUsersNew)
in div (created by DialogContent)
in DialogContent (created by WithStyles(DialogContent))
in WithStyles(DialogContent) (created by DialogUsersNew)
in div (created by Paper)
in Paper (created by WithStyles(Paper))
in WithStyles(Paper) (created by DialogUsersNew)
in DialogUsersNew (created by DisplayUsers)
in DisplayUsers (created by DisplayChoice)
in DisplayChoice (created by DisplayMain)
in main (created by DisplayMain)
in div (created by DisplayMain)
in div (created by DisplayMain)
in DisplayMain (created by App)
in App
in AppContainer
失败的 TypeScript 类:
import
Button,
DialogActions,
DialogContent,
Paper,
TextField,
Typography,
from '@material-ui/core';
import * as React from 'react';
import User from '../../../data/model/user';
import AddNewUserResponse from '../../../data/services/add-new-user-response';
import DialogMessage from '../../dialogs/dialog-message';
export interface DialogUsersNewProps
onClickSave(user: User): Promise<AddNewUserResponse>;
onClickAbort(): void;
export interface DialogUsersNewState
savingErrorMessage: string;
export class DialogUsersNew extends React.Component<DialogUsersNewProps, DialogUsersNewState>
private textFieldUsername: string;
private textFieldPassword: string;
public constructor(props: any)
super(props);
this.state =
savingErrorMessage: '',
;
public render()
return <Paper>
this.state.savingErrorMessage !== '' &&
<DialogMessage title='Saving error' content=this.state.savingErrorMessage />
<DialogContent>
<Typography variant='h5'>New user</Typography>
<TextField label='Username'
value=this.textFieldUsername
className='w-100 fieldMargin'
onChange=(e: any) => this.onChangeTextFieldUsername(e.target.value)
margin='normal'/>
<TextField label='Password'
type='password'
value=this.textFieldPassword
className='w-100 fieldMargin'
onChange=(e: any) => this.onChangeTextFieldPassword(e.target.value)
margin='normal'/>
<DialogActions>
<Button onClick=() => this.props.onClickAbort() color='primary'>Abort</Button>
<Button onClick=() => this.onClickSave() color='primary' variant='contained'>Save</Button>
</DialogActions>
</DialogContent>
</Paper>;
private getUser(): User
// Generate new user based on props and textfields.
return
password: this.textFieldPassword,
username: this.textFieldUsername,
;
private onChangeTextFieldUsername(content: string)
// Save textbox change.
this.textFieldUsername = content;
private onChangeTextFieldPassword(content: string)
// Save textbox change.
this.textFieldPassword = content;
private onClickSave()
// Send click save event to parent.
this.props.onClickSave(this.getUser()).then((response: AddNewUserResponse) =>
// Check if success has failed.
if (!response.success)
// Save message in state.
if (response.message)
this.setState(savingErrorMessage: response.message);
else
this.setState(savingErrorMessage: 'Undefined error.');
).catch((response: AddNewUserResponse) =>
// Save message in state.
if (response.message)
this.setState(savingErrorMessage: response.message);
else
this.setState(savingErrorMessage: 'Undefined error.');
);
【问题讨论】:
this
在onClickSave
的捕获中等于什么?
你能把onClickSave
声明为箭头函数吗? private onClickSave = () => ...
@Nicholas 我不确定我是否正确理解了您的问题。 catch
中的this
指的是DialogUsersNew
类中的方法。我正在尝试通过访问setState
方法在DialogUsersNewState
中设置状态。
我在私有函数中使用this
是错误的。它仅适用于 static
函数。
尝试使用前面提到的箭头函数。它将this
绑定到父作用域。
【参考方案1】:
好的,这里发生了两个问题。
首先,当使用 <input />
组件(或像 TextField
这样在后台使用它们的组件)时,如果您想控制它们的值 (value=this.foobar
),您必须始终控制价值。您遇到的问题是 this.textFieldUsername/Password
最初是 undefined
,这将导致如下所述的错误:React - changing an uncontrolled input
其次,在您单击保存按钮之前不会发生这种情况的原因是因为
this.textFieldUsername/Password
不处于 React 状态,这意味着当它改变时不会导致你的 Component 重新渲染,从而延迟了错误的发生。
this.setState
确实会导致您的组件重新渲染,给那些TextField
s 提供this.textFieldUsername/Password
的值,从而导致上述错误。
.catch
块将捕获配对 .then
块中的错误。由于.then
中的this.setState
导致render()
中发生上述错误,因此该错误会冒泡回到您的Promise 并让您无助地落在.catch
块中。我找不到关于此的 SO 帖子,所以这里有一个 MVP 证明是这样的:https://codesandbox.io/s/2wzvj6p7v0。
最简单的解决办法是
private textFieldUsername: string = "";
private textFieldPassword: string = "";
【讨论】:
【参考方案2】:错误
您正在呼叫this.props.onClickSave
,但它不起作用。
原因
当你传入道具时,例如<DialogUsersNew onClickSave=/*here*/
确保您保留 this 上下文,例如using an arrow function<DialogUsersNew onClickSave=()=>this.something()
【讨论】:
我想我按照你的建议做了。我通过添加render()
方法更新了上面的帖子。
@Socrates 进行此更改后,您遇到了什么错误?您在 OP 中发布的控制台警告似乎与 onClickSave
无关,而是与 TextField
s 及其 value
道具有关。
@y2bd 调用this.setState(savingErrorMessage: response.message);
时发生错误。方法onClickSave()
由render()
方法中的两个按钮之一调用。以上是关于React setState 未能捕获被拒绝的 Promise的主要内容,如果未能解决你的问题,请参考以下文章