【问题标题】:Why am I getting an error about updating an unmounted component?为什么我在更新未安装的组件时收到错误消息?
【发布时间】:2021-09-28 13:23:13
【问题描述】:

在我的 React 应用程序中,我配置了使用社交媒体和电子邮件和密码的登录。登录有效,但我重定向到主页失败。在控制台中,我收到以下错误:

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
LoginPage@http://localhost:3000/static/js/main.chunk.js:3184:101
Route@http://localhost:3000/static/js/vendors~main.chunk.js:109151:29

我有一个名为AuthProvder 的上下文,其中包含以下内容:

import React, { Component, createContext, useContext, useState, useEffect } from "react";
import firebase from "firebase/app";
import { auth, generateUserDocument, projectFirestore, Providers } from "../config/firebase";
import ILocalLoginData from "../interfaces/locallogindata.interface";
import { IProfile } from "../interfaces/profile.interface";

// const UserContext = createContext<Partial<ContextProps>>({});
const AuthContext = createContext({} as any);

const useAuth = () => {
    return useContext(AuthContext);
}

const AuthProvider = ({children}: any) => {

    const [currentUser, setCurrentUser] = useState<any>(null);
    const [loading, setLoading] = useState(true);

    // create the user account in the app database
    // this only needs to be done for the password provider
    // because social media login will create this information
    // for the app
    const signup = async (
        provider: firebase.auth.AuthProvider,
        values?: IProfile
    ) => {

        if (provider.providerId === "password")
        {
            const creds = auth.createUserWithEmailAndPassword(values?.email as string, values?.password as string);

            // add a document to the user collection
            await projectFirestore.collection("users").add({...values});

            return creds;
        }

        return
    }

    const login = (
        provider: firebase.auth.AuthProvider,
        values?: ILocalLoginData
    ) => {
        

        // perform the correct login based on the provider
        if (provider.providerId === "password")
        {
            return auth.signInWithEmailAndPassword(values?.email as string, values?.password as string);
        } else {
            return auth.signInWithPopup(provider);
        }
    }

    const logout = () => {
        return auth.signOut();
    }


    useEffect(() => {
        const unsubscribe = auth.onAuthStateChanged(async user => {

            const usr = await generateUserDocument(user);

            setCurrentUser(usr);
            setLoading(false);

        })

        return unsubscribe;
    }, []);

    const value = {
        currentUser,
        login,
        logout,
        signup
    };

    return (
        <AuthContext.Provider value={value}>
            {!loading && children} 
        </AuthContext.Provider>
    )
}

export { AuthProvider, AuthContext, useAuth };

可以看出,我正在从 useEffect 函数返回一个 unsubscribe 函数。

LoginPage.tsx如下:

import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import firebase from "firebase/app";

import IPageProps from "../../interfaces/page.interface";
import { SignIn } from "../../modules/auth";
import { Providers } from "../../config/firebase";
 
import SiteNavbar from "../../comps/Navbar";
import ILocalLoginData from "../../interfaces/locallogindata.interface";

import { useAuth } from "../../contexts/AuthProvider";

const LoginPage: React.FC<IPageProps> = props => {
    const [authenticating, setAuthenticating] = useState<boolean>(false);
    
    const [error, setError] = useState<string>('');
    const [loading, setLoading] = useState<boolean>(false);
    const [values, setValues] = useState<ILocalLoginData>({
        email: "",
        password: ""
    });
    const { login } = useAuth();
    const history = useHistory();  

    const handleLogin = async (e: any, provider: firebase.auth.AuthProvider) => {
        e.preventDefault();

        try {
            setError("");
            setLoading(true);
            await login(provider, values);
            history.push("/");
        } catch {
            setError("Failed to log in");
        }

        setLoading(false);
    }

    // Handle the changes made in the login form so that the values
    // can be extracted
    const handleChange = (e: any) => {
        e.persist();
        setValues(values => ({
            ...values,
            [e.target.name]: e.target.value
        }));
    }

    return (
        <div>
        <SiteNavbar />

        <div className="flex h-screen bg-yellow-700">

            <div className="max-w-xs w-full m-auto bg-yellow-100 rounded p-5">
                <header>
                    <img alt="" className="w-20 mx-auto mb-5" src="https://img.icons8.com/fluent/96/000000/tiger.png" />
                </header>
                <form>
                    <div>
                        <label className="block mb-2 text-yellow-500" htmlFor="email">Email</label>
                        <input className="w-full p-2 mb-6 text-yellow-700 border-b-2 border-yellow-500 outline-none focus:bg-gray-300"
                            type="text"
                            name="email"
                            value={values.email}
                            placeholder="Enter your email address"
                            onChange={handleChange} />
                    </div>
                    <div>
                        <label className="block mb-2 text-yellow-500" htmlFor="password">Password</label>
                        <input className="w-full p-2 mb-6 text-yellow-700 border-b-2 border-yellow-500 outline-none focus:bg-gray-300"
                            type="password"
                            name="password"
                            value={values.password}
                            onChange={handleChange} />
                    </div>
                    <div>
                        <button className="w-full bg-yellow-700 hover:bg-pink-700 text-white font-bold py-2 px-4 mb-6 rounded"
                            onClick={(e) => handleLogin(e, Providers.email)}>
                            Login
                        </button>
                    </div>
                </form>

                <div>
                    <button
                        className="w-full bg-gray-100 hover:bg-pink-700 text-black font-bold py-2 px-4 mb-6 rounded"
                        disabled={authenticating}
                        onClick={(e) => handleLogin(e, Providers.google)}
                    >
                        <i className="fa fa-google"></i>   Login in with Google
                    </button>
                    <button
                        className="w-full bg-indigo-700 hover:bg-pink-700 text-white font-bold py-2 px-4 mb-6 rounded"
                        disabled={authenticating}
                        onClick={(e) => handleLogin(e, Providers.facebook)}
                    >
                        <i className="fa fa-facebook-square"></i>   Login in with Facebook
                    </button>
                </div>
            </div>
        </div>
        </div>
    )
}

export default LoginPage;

获取所有用户详细信息的函数是:

import firebase from 'firebase/app';
import 'firebase/storage';
import 'firebase/firestore';
import 'firebase/auth';
import config from './config';

const Firebase = firebase.initializeApp(config.firebase);

const auth = firebase.auth();
const projectStorage = Firebase.storage();
const projectFirestore = Firebase.firestore();
const timestamp = firebase.firestore.FieldValue.serverTimestamp;

const Providers = {
    google: new firebase.auth.GoogleAuthProvider(),
    email: new firebase.auth.EmailAuthProvider(),
    facebook: new firebase.auth.FacebookAuthProvider(),
}

// Create function that will add any new uses to the user table
const generateUserDocument = async (user: any, additionalData: any = null) => {

    // return if no user has been set
    if (!user) return;

    const userRef = projectFirestore.doc(`users/${user.uid}`);
    const snapshot = await userRef.get();

    // if a snapshot does not exist, e.g. the user does not exist
    // add them to the the collection
    if (!snapshot.exists) {
        const { email, displayName, photoURL } = user;
        try {
            await userRef.set({
                displayName,
                email,
                photoURL,
                enabled: true,
                permitted: false,
                ...additionalData,
            });
        } catch (error) {
            console.error("Error creating user document", error);
        }
    }

    return getUserDocument(user.uid);
};

// get information about the user
const getUserDocument = async (uid: string) => {
    if (!uid) return null;

    try {
        const userDocument = await projectFirestore.doc(`users/${uid}`).get();

        // if the user is not permitted or not enabled return null
        if (userDocument.exists) {
            const doc = userDocument.data();

            if (!doc?.permitted) {
                console.log("not permitted");
                return null;
            } else if (!doc?.enabled) {
                console.log("not enabled");
                return null;
            } else {
                console.log("happy days");
                return {
                    uid,
                    ...userDocument.data(),
                };
            }
        }
    } catch (error) {
        console.error("Error fetching user", error);
    }
};



export { projectStorage, projectFirestore, timestamp, auth, Providers, generateUserDocument};

控制台日志只是为了让我了解正在发生的事情。事实上,我得到了“快乐的日子”输出,但是在上面的错误之后。这让我感到困惑,因为它发生在 useEffect 块中的 onAuthStateChanged 函数中,为什么它没有安装?

我假设这是我缺少的一些简单的东西。该应用程序可以工作并且该人已登录,但令人讨厌的是重定向到主页不起作用。手动导航到主页。

【问题讨论】:

    标签: reactjs typescript firebase


    【解决方案1】:

    我相信在导航期间调用来自身份验证框架的回调,或者回调不是异步的,并且允许在完成之前进行内部路由。如果组件仍然挂载,您可以在回调中检查状态。

    例如,你可以新建一个新的钩子

           function useIsMounted(): { current: boolean } {
              const componentIsMounted = useRef(true)
              useEffect(() => {
                return () => { componentIsMounted.current = false }
              }, [])
              return componentIsMounted
            }
    

    你可以像下面这样使用这个新的钩子

          const isMounted = useIsMounted();
          useEffect(() => {
            return auth.onAuthStateChanged(async user => {
                ...
                if (isMounted.current) {
                    setCurrentUser(usr);
                    setLoading(false);
                }
            })
           }, []);
    

    【讨论】:

    • @Stansilav-sloc 谢谢你已经摆脱了错误,我用户已经登录。但是重定向history.push("/") 不起作用。我还缺少其他东西吗?
    • @RussellSeymour 我认为 history.push("/") 是首先删除 AuthProvider 组件的原因。我的前提是 await login(...) 进行了身份验证,导航被触发 history.push("/"),AuthProvider 被移除,但 onAuthStateChange 被调用。我不知道您的路由设置如何,可能是那里有另一个不同步的来源。考虑将导航移动到 onAuthStateChanged 并通过 login() 设置重定向到哪里...
    • @stanislav-sloc 好的,谢谢。你的建议让我走了,所以我会将你的答案标记为已接受。这不是一个永久站点,所以我不太担心,但对于未来的应用程序,我会考虑不使用 AuthComponent - 我认为这是在 React 中完成的方式,所以我很糟糕。如果有一个有效的当前用户,我使用&lt;Redirect to="/"/&gt; 使重定向工作。不是很好,但它会做。
    猜你喜欢
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-19
    • 1970-01-01
    • 2020-04-01
    • 2020-03-06
    • 2016-04-24
    相关资源
    最近更新 更多