🚸(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:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { useUser } from './api/useUser'
|
||||
export { authUrl } from './utils/authUrl'
|
||||
export { logoutUrl } from './utils/logoutUrl'
|
||||
export { RenderIfUserFetched } from './components/RenderIfUserFetched'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user