React setState 未能捕获被拒绝的 Promise

Posted

技术标签:

【中文标题】React setState 未能捕获被拒绝的 Promise【英文标题】:React setState fails in catch of rejected Promise 【发布时间】:2019-08-10 16:12:40 【问题描述】:

在 Promise 的 catch 中设置状态时出现错误。在下面的示例中,Promise 的 catchonClickSave() 方法中。我相信我得到了错误,因为我误解了我所在的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.');
            
        );
    

【问题讨论】:

thisonClickSave 的捕获中等于什么? 你能把onClickSave声明为箭头函数吗? private onClickSave = () =&gt; ... @Nicholas 我不确定我是否正确理解了您的问题。 catch 中的this 指的是DialogUsersNew 类中的方法。我正在尝试通过访问setState 方法在DialogUsersNewState 中设置状态。 我在私有函数中使用this 是错误的。它仅适用于 static 函数。 尝试使用前面提到的箭头函数。它将this 绑定到父作用域。 【参考方案1】:

好的,这里发生了两个问题。

首先,当使用 &lt;input /&gt; 组件(或像 TextField 这样在后台使用它们的组件)时,如果您想控制它们的值 (value=this.foobar),您必须始终控制价值。您遇到的问题是 this.textFieldUsername/Password 最初是 undefined,这将导致如下所述的错误:React - changing an uncontrolled input

其次,在您单击保存按钮之前不会发生这种情况的原因是因为

    this.textFieldUsername/Password 不处于 React 状态,这意味着当它改变时不会导致你的 Component 重新渲染,从而延迟了错误的发生。 this.setState 确实会导致您的组件重新渲染,给那些TextFields 提供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,但它不起作用。

原因

当你传入道具时,例如&lt;DialogUsersNew onClickSave=/*here*/ 确保您保留 this 上下文,例如using an arrow function&lt;DialogUsersNew onClickSave=()=&gt;this.something()

【讨论】:

我想我按照你的建议做了。我通过添加render() 方法更新了上面的帖子。 @Socrates 进行此更改后,您遇到了什么错误?您在 OP 中发布的控制台警告似乎与 onClickSave 无关,而是与 TextFields 及其 value 道具有关。 @y2bd 调用this.setState(savingErrorMessage: response.message);时发生错误。方法onClickSave()render() 方法中的两个按钮之一调用。

以上是关于React setState 未能捕获被拒绝的 Promise的主要内容,如果未能解决你的问题,请参考以下文章

React setState 未定义?

Laravel - React,强制 Axios 进行拒绝/捕获

iPad应用程序被拒绝,“未能及时启动”崩溃

Gitlab-runner 未能删除权限被拒绝

React 3 - setState()

未能执行目标... maven-install-plugin ... 无法安装工件...(访问被拒绝)