Refactor <MoreComponents /> fetching/error handling behavior
This commit is contained in:
parent
9810514c76
commit
e034e3766b
@ -55,8 +55,6 @@ export default async function GridPage() {
|
|||||||
</div>}
|
</div>}
|
||||||
sideHiddenOnMobile
|
sideHiddenOnMobile
|
||||||
/>
|
/>
|
||||||
: <Suspense>
|
: <PhotosEmptyState />
|
||||||
<PhotosEmptyState />
|
|
||||||
</Suspense>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,8 +96,8 @@ export default function RootLayout({
|
|||||||
</ThemeProviderClient>
|
</ThemeProviderClient>
|
||||||
</MoreComponentsProvider>
|
</MoreComponentsProvider>
|
||||||
</AppStateProvider>
|
</AppStateProvider>
|
||||||
<Analytics />
|
<Analytics debug={false} />
|
||||||
<SpeedInsights />
|
<SpeedInsights debug={false} />
|
||||||
<PhotoEscapeHandler />
|
<PhotoEscapeHandler />
|
||||||
<ToasterWithThemes />
|
<ToasterWithThemes />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
125
src/components/AnimateChildren.tsx
Normal file
125
src/components/AnimateChildren.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Children, ReactNode, useRef } from 'react';
|
||||||
|
import { Variant, motion } from 'framer-motion';
|
||||||
|
import { useAppState } from '@/state/AppState';
|
||||||
|
import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion';
|
||||||
|
|
||||||
|
export type AnimationType = 'none' | 'scale' | 'left' | 'right' | 'bottom';
|
||||||
|
|
||||||
|
export interface AnimationConfig {
|
||||||
|
type?: AnimationType
|
||||||
|
duration?: number
|
||||||
|
staggerDelay?: number
|
||||||
|
scaleOffset?: number
|
||||||
|
distanceOffset?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends AnimationConfig {
|
||||||
|
children: ReactNode
|
||||||
|
className?: string
|
||||||
|
classNameItem?: string
|
||||||
|
animateFromAppState?: boolean
|
||||||
|
animateOnFirstLoadOnly?: boolean
|
||||||
|
staggerOnFirstLoadOnly?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AnimateChildren({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
classNameItem,
|
||||||
|
type = 'scale',
|
||||||
|
duration = 0.6,
|
||||||
|
staggerDelay = 0.1,
|
||||||
|
scaleOffset = 0.9,
|
||||||
|
distanceOffset = 20,
|
||||||
|
animateFromAppState,
|
||||||
|
animateOnFirstLoadOnly,
|
||||||
|
staggerOnFirstLoadOnly,
|
||||||
|
}: Props) {
|
||||||
|
const {
|
||||||
|
hasLoaded,
|
||||||
|
nextPhotoAnimation,
|
||||||
|
clearNextPhotoAnimation,
|
||||||
|
} = useAppState();
|
||||||
|
|
||||||
|
const prefersReducedMotion = usePrefersReducedMotion();
|
||||||
|
|
||||||
|
const hasLoadedInitial = useRef(hasLoaded);
|
||||||
|
const nextPhotoAnimationInitial = useRef(nextPhotoAnimation);
|
||||||
|
|
||||||
|
const shouldAnimate = type !== 'none' &&
|
||||||
|
!prefersReducedMotion &&
|
||||||
|
!(animateOnFirstLoadOnly && hasLoadedInitial.current);
|
||||||
|
const shouldStagger =
|
||||||
|
!(staggerOnFirstLoadOnly && hasLoadedInitial.current);
|
||||||
|
|
||||||
|
const typeResolved = animateFromAppState
|
||||||
|
? (nextPhotoAnimationInitial.current?.type ?? type)
|
||||||
|
: type;
|
||||||
|
|
||||||
|
const durationResolved = animateFromAppState
|
||||||
|
? (nextPhotoAnimationInitial.current?.duration ?? duration)
|
||||||
|
: duration;
|
||||||
|
|
||||||
|
const getInitialVariant = (): Variant => {
|
||||||
|
switch (typeResolved) {
|
||||||
|
case 'left': return {
|
||||||
|
opacity: 0,
|
||||||
|
transform: `translateX(${distanceOffset}px)`,
|
||||||
|
};
|
||||||
|
case 'right': return {
|
||||||
|
opacity: 0,
|
||||||
|
transform: `translateX(${-distanceOffset}px)`,
|
||||||
|
};
|
||||||
|
case 'bottom': return {
|
||||||
|
opacity: 0,
|
||||||
|
transform: `translateY(${distanceOffset}px)`,
|
||||||
|
};
|
||||||
|
default: return {
|
||||||
|
opacity: 0,
|
||||||
|
transform: `translateY(${distanceOffset}px) scale(${scaleOffset})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className={className}
|
||||||
|
initial={shouldAnimate ? 'hidden' : false}
|
||||||
|
animate="show"
|
||||||
|
variants={shouldStagger
|
||||||
|
? {
|
||||||
|
show: {
|
||||||
|
transition: {
|
||||||
|
staggerChildren: staggerDelay,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} : undefined}
|
||||||
|
onAnimationComplete={() => {
|
||||||
|
if (animateFromAppState) {
|
||||||
|
clearNextPhotoAnimation?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Children.toArray(children).map((item, index) =>
|
||||||
|
<motion.div
|
||||||
|
key={index}
|
||||||
|
className={classNameItem}
|
||||||
|
variants={{
|
||||||
|
hidden: getInitialVariant(),
|
||||||
|
show: {
|
||||||
|
opacity: 1,
|
||||||
|
transform: 'translateX(0) translateY(0) scale(1)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: durationResolved,
|
||||||
|
easing: 'easeOut',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</motion.div>)}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import { clsx } from 'clsx/lite';
|
import { clsx } from 'clsx/lite';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
import { BiErrorAlt } from 'react-icons/bi';
|
import { BiErrorAlt } from 'react-icons/bi';
|
||||||
|
|
||||||
export default function ErrorNote({
|
export default function ErrorNote({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import {
|
|||||||
} from '@/state/MoreComponentsState';
|
} from '@/state/MoreComponentsState';
|
||||||
|
|
||||||
const MAX_ATTEMPTS_PER_REQUEST = 5;
|
const MAX_ATTEMPTS_PER_REQUEST = 5;
|
||||||
const MAX_TOTAL_REQUESTS = 500;
|
const MAX_TOTAL_REQUESTS = 100;
|
||||||
const RETRY_DELAY_IN_SECONDS = 1.5;
|
const RETRY_DELAY_IN_SECONDS = 1;
|
||||||
|
|
||||||
export default function MoreComponents({
|
export default function MoreComponents({
|
||||||
stateKey,
|
stateKey,
|
||||||
@ -22,6 +22,7 @@ export default function MoreComponents({
|
|||||||
triggerOnView = true,
|
triggerOnView = true,
|
||||||
prefetch = true,
|
prefetch = true,
|
||||||
wrapMoreButtonInSiteGrid,
|
wrapMoreButtonInSiteGrid,
|
||||||
|
debug,
|
||||||
}: {
|
}: {
|
||||||
stateKey: MoreComponentsKey
|
stateKey: MoreComponentsKey
|
||||||
initialOffset: number
|
initialOffset: number
|
||||||
@ -35,6 +36,7 @@ export default function MoreComponents({
|
|||||||
triggerOnView?: boolean
|
triggerOnView?: boolean
|
||||||
prefetch?: boolean
|
prefetch?: boolean
|
||||||
wrapMoreButtonInSiteGrid?: boolean
|
wrapMoreButtonInSiteGrid?: boolean
|
||||||
|
debug?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { state, setStateForKey } = useMoreComponentsState();
|
const { state, setStateForKey } = useMoreComponentsState();
|
||||||
|
|
||||||
@ -44,57 +46,72 @@ export default function MoreComponents({
|
|||||||
[setStateForKey, stateKey]);
|
[setStateForKey, stateKey]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
indexToView,
|
|
||||||
indexLoaded,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
lastIndexToLoad,
|
indexInView,
|
||||||
|
finalIndex,
|
||||||
|
didReachMaximumRequests,
|
||||||
components,
|
components,
|
||||||
} = state[stateKey];
|
} = state[stateKey];
|
||||||
|
|
||||||
// When prefetching, always stay one request ahead of what's visible
|
// When prefetching, always stay one request ahead of what's visible
|
||||||
const indexToLoad = lastIndexToLoad
|
const furthestIndexToLoad = Math.min(
|
||||||
?? (prefetch ? indexToView + 1 : indexToView);
|
prefetch ? (indexInView ?? 0) + 1 : (indexInView ?? 0),
|
||||||
|
finalIndex ?? Infinity,
|
||||||
|
);
|
||||||
|
|
||||||
|
const indexToLoad = Math.min(
|
||||||
|
components.length,
|
||||||
|
furthestIndexToLoad,
|
||||||
|
);
|
||||||
|
|
||||||
const attemptsPerRequest = useRef(0);
|
const attemptsPerRequest = useRef(0);
|
||||||
const totalRequests = useRef(0);
|
const totalRequests = useRef(0);
|
||||||
|
|
||||||
const showMoreButton = (
|
const showMoreButton =
|
||||||
lastIndexToLoad === undefined ||
|
isLoading ||
|
||||||
lastIndexToLoad > indexToView
|
finalIndex === undefined ||
|
||||||
) && (
|
finalIndex >= components.length;
|
||||||
attemptsPerRequest.current < MAX_ATTEMPTS_PER_REQUEST &&
|
|
||||||
totalRequests.current < MAX_TOTAL_REQUESTS
|
const currentTimeout = useRef<NodeJS.Timeout>();
|
||||||
);
|
|
||||||
|
|
||||||
const attempt = useCallback(() => {
|
const attempt = useCallback(() => {
|
||||||
const handleError = () => {
|
const handleError = () => {
|
||||||
setTimeout(() => {
|
if (currentTimeout.current) {
|
||||||
attempt();
|
clearTimeout(currentTimeout.current);
|
||||||
}, RETRY_DELAY_IN_SECONDS * 1000);
|
}
|
||||||
|
currentTimeout.current =
|
||||||
|
setTimeout(attempt, RETRY_DELAY_IN_SECONDS * 1000);
|
||||||
};
|
};
|
||||||
if (attemptsPerRequest.current < MAX_ATTEMPTS_PER_REQUEST) {
|
if (attemptsPerRequest.current < MAX_ATTEMPTS_PER_REQUEST) {
|
||||||
if (totalRequests.current < MAX_TOTAL_REQUESTS) {
|
if (totalRequests.current < MAX_TOTAL_REQUESTS) {
|
||||||
attemptsPerRequest.current += 1;
|
attemptsPerRequest.current += 1;
|
||||||
totalRequests.current += 1;
|
totalRequests.current += 1;
|
||||||
setState({ isLoading: true });
|
setState({ isLoading: true });
|
||||||
|
if (debug) {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
console.log(`GETTING INDEX: #${indexToLoad}, ATTEMPT: #${attemptsPerRequest.current}`);
|
||||||
|
}
|
||||||
getNextComponent(
|
getNextComponent(
|
||||||
initialOffset + (indexToLoad - 1) * itemsPerRequest,
|
initialOffset + indexToLoad * itemsPerRequest,
|
||||||
itemsPerRequest,
|
itemsPerRequest,
|
||||||
)
|
)
|
||||||
.then(({ nextComponent, isFinished, didFail }) => {
|
.then(({ nextComponent, isFinished, didFail }) => {
|
||||||
if (!didFail && nextComponent) {
|
if (!didFail) {
|
||||||
|
attemptsPerRequest.current = 0;
|
||||||
setState(state => {
|
setState(state => {
|
||||||
const updatedComponents = [...state.components];
|
const updatedComponents = [...state.components];
|
||||||
updatedComponents[indexToLoad] = nextComponent;
|
if (nextComponent) {
|
||||||
|
updatedComponents[indexToLoad] = nextComponent;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
components: updatedComponents,
|
...nextComponent && { components: updatedComponents},
|
||||||
indexLoaded: indexToLoad,
|
latestIndexLoaded: indexToLoad,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
...isFinished && { lastIndexToLoad: indexToLoad },
|
didReachMaximumRequests: false,
|
||||||
|
...isFinished && { finalIndex: indexToLoad },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
attemptsPerRequest.current = 0;
|
|
||||||
} else {
|
} else {
|
||||||
handleError();
|
handleError();
|
||||||
}
|
}
|
||||||
@ -104,7 +121,10 @@ export default function MoreComponents({
|
|||||||
console.log(
|
console.log(
|
||||||
`Max total attempts reached (${MAX_TOTAL_REQUESTS})`
|
`Max total attempts reached (${MAX_TOTAL_REQUESTS})`
|
||||||
);
|
);
|
||||||
setState({ isLoading: false });
|
setState({
|
||||||
|
isLoading: false,
|
||||||
|
didReachMaximumRequests: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
@ -112,7 +132,7 @@ export default function MoreComponents({
|
|||||||
);
|
);
|
||||||
setState({
|
setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
haveAttemptsPerRequestBeenExceeded: true,
|
didReachMaximumRequests: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@ -121,32 +141,37 @@ export default function MoreComponents({
|
|||||||
initialOffset,
|
initialOffset,
|
||||||
indexToLoad,
|
indexToLoad,
|
||||||
itemsPerRequest,
|
itemsPerRequest,
|
||||||
|
debug,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isLoading &&
|
!isLoading &&
|
||||||
indexToLoad >= indexToView &&
|
indexToLoad >= components.length
|
||||||
indexToLoad > indexLoaded
|
|
||||||
) {
|
) {
|
||||||
console.log('Attempting', { isLoading });
|
|
||||||
attempt();
|
attempt();
|
||||||
}
|
}
|
||||||
}, [
|
}, [isLoading, indexToLoad, indexInView, attempt, components.length]);
|
||||||
isLoading,
|
|
||||||
indexToLoad,
|
|
||||||
indexToView,
|
|
||||||
indexLoaded,
|
|
||||||
attempt,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
const resetRequestsAndRetry = useCallback(() => {
|
||||||
|
attemptsPerRequest.current = 0;
|
||||||
|
totalRequests.current = 0;
|
||||||
|
setState({ didReachMaximumRequests: false });
|
||||||
|
attempt();
|
||||||
|
}, [attempt, setState]);
|
||||||
|
|
||||||
const advance = useCallback(() => {
|
const advance = useCallback(() => {
|
||||||
if (indexToView <= indexLoaded) {
|
if (indexInView === undefined) {
|
||||||
setState({ indexToView: indexToView + 1 });
|
setState({ indexInView: 0 });
|
||||||
|
} else if (
|
||||||
|
(indexInView <= components.length) &&
|
||||||
|
(finalIndex === undefined || indexInView < finalIndex)
|
||||||
|
) {
|
||||||
|
setState({ indexInView: indexInView + 1});
|
||||||
}
|
}
|
||||||
}, [setState, indexToView, indexLoaded]);
|
}, [components.length, finalIndex, indexInView, setState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only add observer if button is rendered
|
// Only add observer if button is rendered
|
||||||
@ -168,22 +193,26 @@ export default function MoreComponents({
|
|||||||
<button
|
<button
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
className="block w-full subtle"
|
className="block w-full subtle"
|
||||||
onClick={!triggerOnView ? advance : undefined}
|
onClick={didReachMaximumRequests ? resetRequestsAndRetry : advance}
|
||||||
disabled={triggerOnView || isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{isLoading || triggerOnView
|
{isLoading
|
||||||
? <span className="relative inline-block translate-y-[3px]">
|
? <span className="relative inline-block translate-y-[3px]">
|
||||||
<Spinner size={16} />
|
<Spinner size={16} />
|
||||||
</span>
|
</span>
|
||||||
: label}
|
: didReachMaximumRequests
|
||||||
|
? 'Try again …'
|
||||||
|
: label}
|
||||||
</button>;
|
</button>;
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>{components.slice(0, indexToView + 1)}</div>
|
<div>{components.slice(0, (indexInView ?? 0) + 1)}</div>
|
||||||
{(showMoreButton || true) && wrapMoreButtonInSiteGrid
|
{showMoreButton && (
|
||||||
? <SiteGrid contentMain={renderMoreButton()} />
|
wrapMoreButtonInSiteGrid
|
||||||
: renderMoreButton()}
|
? <SiteGrid contentMain={renderMoreButton()} />
|
||||||
|
: renderMoreButton()
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import MoreComponents from '@/components/MoreComponents';
|
import MoreComponents from '@/components/MoreComponents';
|
||||||
import { getPhotosCached } from '@/photo/cache';
|
import { getPhotosCached } from '@/photo/cache';
|
||||||
import PhotoGrid from './PhotoGrid';
|
import PhotoGrid from './PhotoGrid';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export function MorePhotosGrid({
|
export function MorePhotosGrid({
|
||||||
initialOffset,
|
initialOffset,
|
||||||
@ -11,32 +12,38 @@ export function MorePhotosGrid({
|
|||||||
itemsPerRequest: number
|
itemsPerRequest: number
|
||||||
totalPhotosCount: number
|
totalPhotosCount: number
|
||||||
}) {
|
}) {
|
||||||
|
const getNextComponent = useCallback(async (
|
||||||
|
offset: number,
|
||||||
|
limit: number,
|
||||||
|
) => {
|
||||||
|
'use server';
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV === 'development' &&
|
||||||
|
Math.random() < 0.5
|
||||||
|
) {
|
||||||
|
return { didFail: true };
|
||||||
|
}
|
||||||
|
const photos = await getPhotosCached({ limit: offset + limit })
|
||||||
|
.catch(() => undefined);
|
||||||
|
if (!photos) {
|
||||||
|
return { didFail: true };
|
||||||
|
} else {
|
||||||
|
const nextPhotos = photos.slice(offset);
|
||||||
|
return {
|
||||||
|
...nextPhotos.length > 0 && {
|
||||||
|
nextComponent: <PhotoGrid photos={nextPhotos} />,
|
||||||
|
},
|
||||||
|
isFinished: offset + limit >= totalPhotosCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [totalPhotosCount]);
|
||||||
return (
|
return (
|
||||||
<MoreComponents
|
<MoreComponents
|
||||||
stateKey="PhotosGrid"
|
stateKey="PhotosGrid"
|
||||||
label="More photos"
|
label="More photos"
|
||||||
initialOffset={initialOffset}
|
initialOffset={initialOffset}
|
||||||
itemsPerRequest={itemsPerRequest}
|
itemsPerRequest={itemsPerRequest}
|
||||||
getNextComponent={async (offset, limit) => {
|
getNextComponent={getNextComponent}
|
||||||
'use server';
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV === 'development' &&
|
|
||||||
Math.random() < 0.95
|
|
||||||
) {
|
|
||||||
return { didFail: true };
|
|
||||||
}
|
|
||||||
const photos = await getPhotosCached({ limit: offset + limit })
|
|
||||||
.catch(() => undefined);
|
|
||||||
if (!photos) {
|
|
||||||
return { didFail: true };
|
|
||||||
} else {
|
|
||||||
const nextPhotos = photos.slice(offset);
|
|
||||||
return {
|
|
||||||
nextComponent: <PhotoGrid photos={nextPhotos} />,
|
|
||||||
isFinished: offset + limit >= totalPhotosCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import MoreComponents from '@/components/MoreComponents';
|
import MoreComponents from '@/components/MoreComponents';
|
||||||
import PhotosLarge from './PhotosLarge';
|
import PhotosLarge from './PhotosLarge';
|
||||||
import { getPhotosCached } from '@/photo/cache';
|
import { getPhotosCached } from '@/photo/cache';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export function MorePhotosRoot({
|
export function MorePhotosRoot({
|
||||||
initialOffset,
|
initialOffset,
|
||||||
@ -11,32 +12,36 @@ export function MorePhotosRoot({
|
|||||||
itemsPerRequest: number
|
itemsPerRequest: number
|
||||||
totalPhotosCount: number
|
totalPhotosCount: number
|
||||||
}) {
|
}) {
|
||||||
|
const getNextComponent = useCallback(async (
|
||||||
|
offset: number,
|
||||||
|
limit: number,
|
||||||
|
) => {
|
||||||
|
'use server';
|
||||||
|
if (
|
||||||
|
process.env.NODE_ENV === 'development' &&
|
||||||
|
Math.random() < 0.5
|
||||||
|
) {
|
||||||
|
return { didFail: true };
|
||||||
|
}
|
||||||
|
const photos = await getPhotosCached({ limit: offset + limit })
|
||||||
|
.catch(() => undefined);
|
||||||
|
if (!photos) {
|
||||||
|
return { didFail: true };
|
||||||
|
} else {
|
||||||
|
const nextPhotos = photos.slice(offset);
|
||||||
|
return {
|
||||||
|
nextComponent: <PhotosLarge photos={nextPhotos} />,
|
||||||
|
isFinished: offset + limit >= totalPhotosCount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [totalPhotosCount]);
|
||||||
return (
|
return (
|
||||||
<MoreComponents
|
<MoreComponents
|
||||||
stateKey="PhotosRoot"
|
stateKey="PhotosRoot"
|
||||||
label="More photos"
|
label="More photos"
|
||||||
initialOffset={initialOffset}
|
initialOffset={initialOffset}
|
||||||
itemsPerRequest={itemsPerRequest}
|
itemsPerRequest={itemsPerRequest}
|
||||||
getNextComponent={async (offset, limit) => {
|
getNextComponent={getNextComponent}
|
||||||
'use server';
|
|
||||||
if (
|
|
||||||
process.env.NODE_ENV === 'development' &&
|
|
||||||
Math.random() < 0.95
|
|
||||||
) {
|
|
||||||
return { didFail: true };
|
|
||||||
}
|
|
||||||
const photos = await getPhotosCached({ limit: offset + limit })
|
|
||||||
.catch(() => undefined);
|
|
||||||
if (!photos) {
|
|
||||||
return { didFail: true };
|
|
||||||
} else {
|
|
||||||
const nextPhotos = photos.slice(offset);
|
|
||||||
return {
|
|
||||||
nextComponent: <PhotosLarge photos={nextPhotos} />,
|
|
||||||
isFinished: offset + limit >= totalPhotosCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
wrapMoreButtonInSiteGrid
|
wrapMoreButtonInSiteGrid
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
|
|||||||
export const INFINITE_SCROLL_MULTIPLE_ROOT =
|
export const INFINITE_SCROLL_MULTIPLE_ROOT =
|
||||||
process.env.NODE_ENV === 'development' ? 2 : 12;
|
process.env.NODE_ENV === 'development' ? 2 : 12;
|
||||||
export const INFINITE_SCROLL_MULTIPLE_GRID = HIGH_DENSITY_GRID
|
export const INFINITE_SCROLL_MULTIPLE_GRID = HIGH_DENSITY_GRID
|
||||||
? process.env.NODE_ENV === 'development' ? 5 : 20
|
? process.env.NODE_ENV === 'development' ? 4 : 20
|
||||||
: process.env.NODE_ENV === 'development' ? 4 : 24;
|
: process.env.NODE_ENV === 'development' ? 4 : 24;
|
||||||
|
|
||||||
export const GRID_THUMBNAILS_TO_SHOW_MAX = 12;
|
export const GRID_THUMBNAILS_TO_SHOW_MAX = 12;
|
||||||
|
|||||||
@ -5,20 +5,17 @@ export type MoreComponentsKey =
|
|||||||
'PhotosGrid';
|
'PhotosGrid';
|
||||||
|
|
||||||
export interface MoreComponentsStateForKey {
|
export interface MoreComponentsStateForKey {
|
||||||
indexToView: number
|
|
||||||
indexLoaded: number
|
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
lastIndexToLoad?: number
|
indexInView?: number
|
||||||
haveAttemptsPerRequestBeenExceeded: boolean
|
finalIndex?: number
|
||||||
|
didReachMaximumRequests: boolean
|
||||||
components: JSX.Element[]
|
components: JSX.Element[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createInitialStateForKey =
|
export const createInitialStateForKey =
|
||||||
(): MoreComponentsStateForKey => ({
|
(): MoreComponentsStateForKey => ({
|
||||||
indexToView: 0,
|
|
||||||
indexLoaded: 0,
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
haveAttemptsPerRequestBeenExceeded: false,
|
didReachMaximumRequests: false,
|
||||||
components: [],
|
components: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user