首页 > 解决方案 > 我可以在使用 Firebase 身份验证时修复“无法在现有状态转换期间更新”错误吗?

问题描述

我正在尝试使用该项目使用 firebase 实现登录firebaseui-web-react

我正在关注人们所做的这个例子firebaseui-web-react您可以在此处找到基本文件,但我会将其粘贴到这个问题上:

/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// React core.
import React from 'react';
import ReactDOM from 'react-dom';

// Firebase.
import firebase from 'firebase/app';
import 'firebase/auth';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';

// Styles
import styles from './app.css'; // This uses CSS modules.
import './firebaseui-styling.global.css'; // Import globally.

// Get the Firebase config from the auto generated file.
const firebaseConfig = require('./firebase-config.json').result;

// Instantiate a Firebase app.
const firebaseApp = firebase.initializeApp(firebaseConfig);

/**
 * The Splash Page containing the login UI.
 */
class App extends React.Component {
  uiConfig = {
    signInFlow: 'popup',
    signInOptions: [
      firebase.auth.GoogleAuthProvider.PROVIDER_ID,
      firebase.auth.EmailAuthProvider.PROVIDER_ID,
    ],
    callbacks: {
      signInSuccessWithAuthResult: () => false,
    },
  };

  state = {
    isSignedIn: undefined,
  };

  /**
   * @inheritDoc
   */
  componentDidMount() {
    this.unregisterAuthObserver = firebaseApp.auth().onAuthStateChanged((user) => {
      this.setState({isSignedIn: !!user});
    });
  }

  /**
   * @inheritDoc
   */
  componentWillUnmount() {
    this.unregisterAuthObserver();
  }

  /**
   * @inheritDoc
   */
  render() {
    return (
      <div className={styles.container}>
        <div className={styles.logo}>
          <i className={styles.logoIcon + ' material-icons'}>photo</i> My App
        </div>
        <div className={styles.caption}>This is a cool demo app</div>
        {this.state.isSignedIn !== undefined && !this.state.isSignedIn &&
          <div>
            <StyledFirebaseAuth className={styles.firebaseUi} uiConfig={this.uiConfig}
                                firebaseAuth={firebaseApp.auth()}/>
          </div>
        }
        {this.state.isSignedIn &&
          <div className={styles.signedIn}>
            Hello {firebaseApp.auth().currentUser.displayName}. You are now signed In!
            <a className={styles.button} onClick={() => firebaseApp.auth().signOut()}>Sign-out</a>
          </div>
        }
      </div>
    );
  }
}

// Load the app in the browser.
ReactDOM.render(<App/>, document.getElementById('app'));

我使用 Typescript 实现了它并且它正在工作。然后我决定重构它,因为我想将有关我的登录提供程序的所有内容保存在不同的类中,以便我可以切换到假实现以进行测试。

所以我以这种方式实现了它:

登录提供者.ts

export default interface LoginProvider {
    unregister(): void
    register(f:(user:any)=>void): void
    actualProvider(): any
    actualConfig(): any
}

FirebaseLoginProvider.ts

import LoginProvider from './LoginProvider';

export default class FirebaseLoginProvider implements LoginProvider {
    private firebaseApp: any
    private uiConfig: any
    private unregisterAuthObserver: () => void

    constructor(firebaseApp: any, uiConfig: any) {
        this.uiConfig = uiConfig
        this.firebaseApp = firebaseApp
    }

    public register(f:(user:any)=>void): void {
        this.unregisterAuthObserver = this.firebaseApp.auth().onAuthStateChanged((user: any) => {
            console.log("state changed " + !!user)
            f(user)
        }).bind(this)
    }

    public unregister(): void {
        this.unregisterAuthObserver()
    }

    public actualProvider(): any {
        return this.firebaseApp.auth()
    }

    public actualConfig(): any {
        return this.uiConfig
    }

}

登录.tsx

import 'firebase/auth';
import './firebase-global.css';

import * as React from 'react';

import { RouteComponentProps, withRouter } from "react-router-dom";

import LoginProvider from './LoginProvider';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';

interface Props {
};

interface State {
  isSignedIn: boolean
}

interface HomeProps extends RouteComponentProps<Props> {
  loginProvider: LoginProvider
}

class Login extends React.Component<HomeProps, State> {

  state = {
    isSignedIn: false
  }

  constructor(props: HomeProps) {
    super(props);
  }

  componentDidMount() {
    this.props.loginProvider.register((user) => {
      this.setState({ isSignedIn: !!user } as State);
    })
  }

  componentWillUnmount() {
    this.props.loginProvider.unregister();
  }

  render() {
    const signedIn = this.state.isSignedIn

    return (
      <div className='container'>
        {!signedIn &&
          <div>
            <StyledFirebaseAuth
              className='firebaseUi'
              uiConfig={this.props.loginProvider.actualConfig()}
              firebaseAuth={this.props.loginProvider.actualProvider()} />
          </div>
        }
        {signedIn && this.props.history.goBack()}
      </div>
    );
  }
}
export default withRouter(Login);

我的实现工作正常,但我在浏览器控制台中收到此警告,我不明白为什么。

Warning: Cannot update during an existing state transition (such as within 'render'). Render methods should be a pure function of props and state.

我也有以下堆栈跟踪:

React 8
    listener createTransitionManager.js:46
    notifyListeners createTransitionManager.js:65
    notifyListeners createTransitionManager.js:64
    setState createBrowserHistory.js:78
    handlePop createBrowserHistory.js:103
    confirmTransitionTo createTransitionManager.js:36
    handlePop createBrowserHistory.js:101
    handlePopState createBrowserHistory.js:85
    go createBrowserHistory.js:214
    goBack createBrowserHistory.js:218
    render Login.tsx:55
    React 13
    componentDidMount Login.tsx:34
    unregisterAuthObserver FirebaseLoginProvider.ts:16
    next index.cjs.js:1303
    sendOne index.cjs.js:1407

关键是我没有看到其他实现的问题,但我只是将东西提取到方法和类中,所以我不明白为什么它给了我这个以前没有的错误。

问题似乎与react-router我尝试做的地方有关:

{signedIn && this.props.history.goBack()}

但是如果我把这个调用放在里面componentDidMount,在firebase登录页面关闭后它就不会被调用。

我可能遗漏了一些关于 React 生命周期的东西。你能帮我解决这个问题吗?

标签: reactjstypescriptfirebasereact-router

解决方案


好的,我解决了。我不知道componentDidUpdate

这是没有警告的新实现:

import 'firebase/auth';
import './firebase-global.css';

import * as React from 'react';

import { RouteComponentProps, withRouter } from "react-router-dom";

import LoginProvider from './LoginProvider';
import StyledFirebaseAuth from 'react-firebaseui/StyledFirebaseAuth';

interface Props {
};

interface State {
  isSignedIn: boolean
}

interface HomeProps extends RouteComponentProps<Props> {
  loginProvider: LoginProvider
}

class Login extends React.Component<HomeProps, State> {

  state = {
    isSignedIn: false
  }

  constructor(props: HomeProps) {
    super(props);
  }

  componentDidMount() {
    this.props.loginProvider.register((user) => {
      this.setState({ isSignedIn: !!user } as State);
    })
  }

  componentDidUpdate() {
    this.goBackWhenLoggedIn();
  }

  componentWillUnmount() {
    this.props.loginProvider.unregister();
  }

  render() {
    const signedIn = this.state.isSignedIn

    return (
      <div className='container'>
        {!signedIn &&
          <div>
            <StyledFirebaseAuth
              className='firebaseUi'
              uiConfig={this.props.loginProvider.actualConfig()}
              firebaseAuth={this.props.loginProvider.actualProvider()} />
          </div>
        }
      </div>
    );
  }

  private goBackWhenLoggedIn() {
    if (this.state.isSignedIn) {
      this.props.history.goBack()
    }
  }
}
export default withRouter(Login);

推荐阅读