我有一个Auth Provider,它包装了我的整个_app.tsx文件。这使我可以使用“ useAuth”钩子,并从应用程序中的任何文件访问用户对象。但是,使用此挂钩有条件地加载我的导航栏时出现问题。在页面加载的前几秒钟,没有找到用户对象,这导致我注销的组件在屏幕上闪烁一秒钟。然后,找到用户对象并加载正确的组件。我不确定该如何解决?我研究了许多不同的方法来解决此问题。 GetInitialProps,GetServerSideProps ...我不确定该怎么用。
gif showing the flashing navbar
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;
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;
答案 0 :(得分:0)
由于您无需为已认证的页面建立索引,因此最简单的解决方案是在进行身份验证时使用框架。就像youtube一样,您将希望有条件地渲染某种骨架,直到直到,上下文的isLoading
值为false
(默认值应为true
)。简而言之,不要仅仅依靠user
状态下的某个用户,而是要依靠{strong>两者 isLoading
状态和user
状态。
演示(虽然它不使用Next,但概念相同):
或者,您可以渲染块整个页面,并使用getInitialProps
(gIP)并将生成的user
值传递给提供者和组件(从gIP返回的任何值都将添加到{{ 1}})。但是,我不建议这样做:禁止您进行静态优化,导致TTFB变慢(到第一个字节的时间),并且技术上将在每页导航中运行,因为无法检索gIP内部的应用上下文值。
作为上下文的替代,您可以使用a Redux provider,它允许您从gIP内设置和检索全局应用程序状态,但是它引入了几层复杂性:使用动作/约简/常数来设置Redux并了解使用客户端Cookie从服务器获取Cookie的细微差别。简而言之,它对开发人员不是很友好。