🚸(frontend) prevent flash of "logged out" user content when loading

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.
This commit is contained in:
Emmanuel Pelletier
2024-07-23 19:18:49 +02:00
parent 57b8a15642
commit 2e081e5e1e
5 changed files with 46 additions and 11 deletions

View File

@@ -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 (
<QueryClientProvider client={queryClient}>
<Suspense fallback={<Screen />}>
<Switch>
<Route path="/" component={HomeRoute} />
<Route path={roomIdRegex} component={RoomRoute} />
<Route component={NotFound} />
</Switch>
<Suspense fallback={null}>
<RenderIfUserFetched>
<Switch>
<Route path="/" component={HomeRoute} />
<Route path={roomIdRegex} component={RoomRoute} />
<Route component={NotFound} />
</Switch>
</RenderIfUserFetched>
<ReactQueryDevtools initialIsOpen={false} />
</Suspense>
</QueryClientProvider>

View File

@@ -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,
}
}

View File

@@ -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
) : (
<LoadingScreen renderTimeout={1000} />
)
}

View File

@@ -1,3 +1,4 @@
export { useUser } from './api/useUser'
export { authUrl } from './utils/authUrl'
export { logoutUrl } from './utils/logoutUrl'
export { RenderIfUserFetched } from './components/RenderIfUserFetched'

View File

@@ -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
}