From 2e081e5e1ec2fc2aeca51b09152fdecb2fc043cf Mon Sep 17 00:00:00 2001 From: Emmanuel Pelletier Date: Tue, 23 Jul 2024 19:18:49 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8(frontend)=20prevent=20flash=20of?= =?UTF-8?q?=20"logged=20out"=20user=20content=20when=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit until now, we concluded that is `isLoggedIn` !== true meant the user wasn't logged in. While it also meant that we are currently loading user info. wrap the whole in something that doesn't render anything until we made the first user request to prevent this behavior. --- src/frontend/src/App.tsx | 16 +++++++++------- src/frontend/src/features/auth/api/useUser.tsx | 11 ++++++++++- .../auth/components/RenderIfUserFetched.tsx | 17 +++++++++++++++++ src/frontend/src/features/auth/index.ts | 1 + src/frontend/src/layout/LoadingScreen.tsx | 12 +++++++++--- 5 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 src/frontend/src/features/auth/components/RenderIfUserFetched.tsx diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 6d7f4956..8e9b7ea4 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -6,11 +6,11 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useLang } from 'hoofd' import { Route, Switch } from 'wouter' -import { Screen } from './layout/Screen' import { HomeRoute } from '@/features/home' import { RoomRoute, roomIdRegex } from '@/features/rooms' import { NotFound } from './routes/NotFound' import './i18n/init' +import { RenderIfUserFetched } from './features/auth' const queryClient = new QueryClient() @@ -19,12 +19,14 @@ function App() { useLang(i18n.language) return ( - }> - - - - - + + + + + + + + diff --git a/src/frontend/src/features/auth/api/useUser.tsx b/src/frontend/src/features/auth/api/useUser.tsx index 6286ca12..84f5e601 100644 --- a/src/frontend/src/features/auth/api/useUser.tsx +++ b/src/frontend/src/features/auth/api/useUser.tsx @@ -2,16 +2,25 @@ import { useQuery } from '@tanstack/react-query' import { keys } from '@/api/queryKeys' import { fetchUser } from './fetchUser' +/** + * returns info about currently logged in user + * + * `isLoggedIn` is undefined while query is loading and true/false when it's done + */ export const useUser = () => { const query = useQuery({ queryKey: [keys.user], queryFn: fetchUser, }) + let isLoggedIn = undefined + if (query.data !== undefined) { + isLoggedIn = query.data !== false + } return { ...query, // if fetchUser returns false, it means the user is not logged in: expose that user: query.data === false ? undefined : query.data, - isLoggedIn: query.data !== undefined && query.data !== false, + isLoggedIn, } } diff --git a/src/frontend/src/features/auth/components/RenderIfUserFetched.tsx b/src/frontend/src/features/auth/components/RenderIfUserFetched.tsx new file mode 100644 index 00000000..276a0133 --- /dev/null +++ b/src/frontend/src/features/auth/components/RenderIfUserFetched.tsx @@ -0,0 +1,17 @@ +import { type ReactNode } from 'react' +import { useUser } from '@/features/auth' +import { LoadingScreen } from '@/layout/LoadingScreen' + +/** + * wrapper that renders children only when user info has been actually fetched + * + * this is helpful to prevent flash of logged-out content for a few milliseconds when user is actually logged in + */ +export const RenderIfUserFetched = ({ children }: { children: ReactNode }) => { + const { isLoggedIn } = useUser() + return isLoggedIn !== undefined ? ( + children + ) : ( + + ) +} diff --git a/src/frontend/src/features/auth/index.ts b/src/frontend/src/features/auth/index.ts index d298a6cb..0b533fea 100644 --- a/src/frontend/src/features/auth/index.ts +++ b/src/frontend/src/features/auth/index.ts @@ -1,3 +1,4 @@ export { useUser } from './api/useUser' export { authUrl } from './utils/authUrl' export { logoutUrl } from './utils/logoutUrl' +export { RenderIfUserFetched } from './components/RenderIfUserFetched' diff --git a/src/frontend/src/layout/LoadingScreen.tsx b/src/frontend/src/layout/LoadingScreen.tsx index 40053ba8..2d58c9e4 100644 --- a/src/frontend/src/layout/LoadingScreen.tsx +++ b/src/frontend/src/layout/LoadingScreen.tsx @@ -3,14 +3,20 @@ import { useTranslation } from 'react-i18next' import { BoxScreen } from './BoxScreen' import { Screen } from './Screen' -export const LoadingScreen = ({ asBox = false }: { asBox?: boolean }) => { +export const LoadingScreen = ({ + asBox = false, + renderTimeout = 500, +}: { + asBox?: boolean + renderTimeout?: number +}) => { const { t } = useTranslation() // show the loading screen only after a little while to prevent flash of texts const [show, setShow] = useState(false) useEffect(() => { - const timeout = setTimeout(() => setShow(true), 500) + const timeout = setTimeout(() => setShow(true), renderTimeout) return () => clearTimeout(timeout) - }, []) + }, [renderTimeout]) if (!show) { return null }