页面加载前如何加载用户

时间:2020-11-05 18:14:35

标签: typescript firebase-authentication next.js

主要问题

我有一个Auth Provider,它包装了我的整个_app.tsx文件。这使我可以使用“ useAuth”钩子,并从应用程序中的任何文件访问用户对象。但是,使用此挂钩有条件地加载我的导航栏时出现问题。在页面加载的前几秒钟,没有找到用户对象,这导致我注销的组件在屏幕上闪烁一秒钟。然后,找到用户对象并加载正确的组件。我不确定该如何解决?我研究了许多不同的方法来解决此问题。 GetInitialProps,GetServerSideProps ...我不确定该怎么用。

[编辑]-问题澄清

  • 如何确保在Next.js中加载组件之前已加载Firebase用户?

使用的技术堆栈

  • Next.js
  • Firebase身份验证
  • MongoDB

问题的根源

gif showing the flashing navbar

代码

_app.tsx

function MyApp({ Component, pageProps }: AppProps) {
    return (
        <>
            <Global
                styles={css`
                    button {
                        border: none;
                    }
                    input {
                        border: none;
                    }
                `}
            />
            <ThemeProvider theme={theme}>
                <CSSReset />
                <AuthProvider>
                    <Page>
                        <Navbar />
                        <Component {...pageProps} />

                        <Footer />
                    </Page>
                </AuthProvider>
            </ThemeProvider>
        </>
    );
}

export default MyApp;

AuthContext.tsx

import React, { useState, useEffect, useContext, createContext } from 'react';
import nookies from 'nookies';
import { firebase } from './initFirebase';
import initFirebase from './initFirebase';
import { auth } from 'firebase';

const AuthContext = createContext<{ user: firebase.User | null }>({
    user: null,
});

export function AuthProvider({ children }: any) {
    const [user, setUser] = useState<firebase.User | null>(null);

    useEffect(() => {
        return firebase.auth().onIdTokenChanged(async (user) => {
            if (!user) {
                setUser(null);
                nookies.set(undefined, 'token', '', {});
                return;
            }

            const token = await user.getIdToken();
            setUser(user);
            setLoading(false);
            nookies.set(undefined, 'token', token, {});
        });
    }, []);

    return (
        <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>
    );
}

// signOut function
export const signOut = async () => {
    await auth().signOut();
};

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

导航栏组件

const Navbar = () => {
    const { user} = useAuth();
    const [loggedOut, setLoggedOut] = useState(false);
    const toast = useToast();
    const router = useRouter();
    const { isOpen, onOpen, onClose } = useDisclosure();

    const handleSignOut = () => {
        signOut()
            .then((result) => {
                toast({
                    title: 'Signed Out',
                    description: 'You have successfully signed out of your account.',
                    status: 'success',
                    duration: 4000,
                    isClosable: true,
                    position: 'top',
                });
                setLoggedOut(true);
                router.push('/');
            })
            .catch((error) => {
                var errorCode = error.code;
                var errorMessage = error.message;
                toast({
                    title: errorCode,
                    description: errorMessage,
                    status: 'error',
                    duration: 4000,
                    isClosable: true,
                    position: 'top',
                });
            });
    };

    return (
        <>
            <NavbarWrapper>
                <NavbarElementWrapper>
                    <LogoLink>
                        <Link href='/'>
                            <Logo />
                        </Link>
                    </LogoLink>
                </NavbarElementWrapper>

                {user ? (
                    <SplitLinks>
                        <NavbarElementWrapper>
                            <TimelineModal />
                        </NavbarElementWrapper>

                        <Menu>
                            <Tooltip label='Account Details' aria-label='account details'>
                                <MenuButton style={{ outline: 'none' }}>
                                    <Avatar
                                        src={user.photoURL}
                                        name={user.displayName || user.email.split('@')[0]}
                                    />
                                </MenuButton>
                            </Tooltip>
                            <MenuList>
                                <MenuGroup>
                                    <MenuItem onClick={() => onOpen()} height='100%'>
                                        <Box as={FaUser} mr='12px' />
                                        Account
                                    </MenuItem>
                                    <MenuItem onClick={() => router.push('/timelines')} height='100%'>
                                        <Box as={MdTimeline} mr='12px' />
                                        Timelines
                                    </MenuItem>
                                    <MenuDivider />
                                    <MenuItem justifyContent='center' style={{ background: 'none' }}>
                                        <Button
                                            onClick={handleSignOut}
                                            leftIcon='arrow-forward'
                                            variantColor='red'
                                        >
                                            Sign Out
                                        </Button>
                                    </MenuItem>
                                </MenuGroup>
                            </MenuList>
                        </Menu>
                    </SplitLinks>
                ) : (
                    <SplitLinks>
                        <LinkHoverWrapper first={true}>
                            <LoginModal />
                        </LinkHoverWrapper>
                        <LinkHoverWrapper>
                            <RegistrationModal />
                        </LinkHoverWrapper>
                    </SplitLinks>
                )}

                <AccountModal isOpen={isOpen} onClose={onClose} />
            </NavbarWrapper>
        </>
    );
};

export default Navbar;

1 个答案:

答案 0 :(得分:0)

由于您无需为已认证的页面建立索引,因此最简单的解决方案是在进行身份验证时使用框架。就像youtube一样,您将希望有条件地渲染某种骨架,直到直到,上下文的isLoading值为false(默认值应为true )。简而言之,不要仅仅依靠user状态下的某个用户,而是要依靠{strong>两者 isLoading状态和user状态。

演示(虽然它不使用Next,但概念相同):

Edit Async Data Context (w/ Skeleton)

或者,您可以渲染块整个页面,并使用getInitialProps(gIP)并将生成的user值传递给提供者和组件(从gIP返回的任何值都将添加到{{ 1}})。但是,我不建议这样做:禁止您进行静态优化,导致TTFB变慢(到第一个字节的时间),并且技术上将在页导航中运行,因为无法检索gIP内部的应用上下文值。

作为上下文的替代,您可以使用a Redux provider,它允许您从gIP内设置和检索全局应用程序状态,但是它引入了几层复杂性:使用动作/约简/常数来设置Redux并了解使用客户端Cookie从服务器获取Cookie的细微差别。简而言之,它对开发人员不是很友好。