fix(web): delay loading skeleton to avoid flash on fast loads
Render the memo list skeleton only after loading exceeds a short delay (SKELETON_LOADING_DELAY_MS). Fast/self-hosted loads now finish before the threshold and never flash the skeleton, while slow loads still show it. Close #6047
This commit is contained in:
parent
e3e4ae1051
commit
9e84f61029
4 changed files with 37 additions and 3 deletions
|
|
@ -7,7 +7,8 @@ import { Button } from "@/components/ui/button";
|
|||
import { userServiceClient } from "@/connect";
|
||||
import { useMemoFilterContext } from "@/contexts/MemoFilterContext";
|
||||
import { useNewMemo } from "@/contexts/NewMemoContext";
|
||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE, SKELETON_LOADING_DELAY_MS } from "@/helpers/consts";
|
||||
import { useDelayedFlag } from "@/hooks/useDelayedFlag";
|
||||
import { useInfiniteMemos } from "@/hooks/useMemoQueries";
|
||||
import { hoistMemoToFront } from "@/hooks/useMemoSorting";
|
||||
import { userKeys } from "@/hooks/useUserQueries";
|
||||
|
|
@ -101,6 +102,9 @@ const PagedMemoList = (props: Props) => {
|
|||
{ enabled: props.enabled ?? true },
|
||||
);
|
||||
|
||||
// Only show the skeleton once loading exceeds the delay, so fast loads don't flash it.
|
||||
const showSkeleton = useDelayedFlag(isLoading, SKELETON_LOADING_DELAY_MS);
|
||||
|
||||
// Flatten pages into a single array of memos
|
||||
const memos = useMemo(() => data?.pages.flatMap((page) => page.memos) || [], [data]);
|
||||
|
||||
|
|
@ -158,9 +162,11 @@ const PagedMemoList = (props: Props) => {
|
|||
const children = (
|
||||
<MentionResolutionProvider contents={sortedMemoList.map((memo) => memo.content)}>
|
||||
<div className="flex flex-col justify-start w-full max-w-2xl mx-auto">
|
||||
{/* Show skeleton loader during initial load */}
|
||||
{/* During initial load, show the skeleton only after the delay; render nothing before then to avoid a flash. */}
|
||||
{isLoading ? (
|
||||
<Skeleton showCreator={props.showCreator} count={4} />
|
||||
showSkeleton ? (
|
||||
<Skeleton showCreator={props.showCreator} count={4} />
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
{showMemoEditor ? (
|
||||
|
|
|
|||
|
|
@ -3,3 +3,7 @@ export const TAB_SPACE_WIDTH = 2;
|
|||
|
||||
// DEFAULT_LIST_MEMOS_PAGE_SIZE is the default page size for list memos request.
|
||||
export const DEFAULT_LIST_MEMOS_PAGE_SIZE = 16;
|
||||
|
||||
// SKELETON_LOADING_DELAY_MS is how long a load must take before the loading skeleton appears.
|
||||
// Loads that finish faster than this never render the skeleton, avoiding a flash on fast/self-hosted networks.
|
||||
export const SKELETON_LOADING_DELAY_MS = 250;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export * from "./useAsyncEffect";
|
|||
export * from "./useCurrentUser";
|
||||
export * from "./useDateFilterNavigation";
|
||||
export * from "./useDebouncedEffect";
|
||||
export * from "./useDelayedFlag";
|
||||
export * from "./useFilteredMemoStats";
|
||||
export * from "./useLoading";
|
||||
export * from "./useLocalStorage";
|
||||
|
|
|
|||
23
web/src/hooks/useDelayedFlag.ts
Normal file
23
web/src/hooks/useDelayedFlag.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* Returns a flag that turns true only after `active` has stayed true for `delay` ms,
|
||||
* and turns false immediately once `active` becomes false.
|
||||
*
|
||||
* Useful for delaying loading indicators (e.g. skeletons) so they don't flash on fast operations.
|
||||
*/
|
||||
export const useDelayedFlag = (active: boolean, delay: number): boolean => {
|
||||
const [delayed, setDelayed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!active) {
|
||||
setDelayed(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = window.setTimeout(() => setDelayed(true), delay);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [active, delay]);
|
||||
|
||||
return delayed;
|
||||
};
|
||||
Loading…
Reference in a new issue