Skip to content

Commit

Permalink
fix(infinite-scroll): add onClick callback
Browse files Browse the repository at this point in the history
  • Loading branch information
igordanchenko committed Aug 5, 2024
1 parent ba6e650 commit 3b35b74
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 12 deletions.
15 changes: 13 additions & 2 deletions docs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,18 +724,29 @@ import { UnstableInfiniteScroll as InfiniteScroll } from "react-photo-album/scro
<tr>
<td><span class="required">children</span></td>
<td>ReactElement</td>
<td>Photo album component. Must be the only child.</td>
<td>The photo album component, which must be the only child.</td>
</tr>
<tr>
<td><span class="required">fetch</span></td>
<td>(index: number) =&gt; Promise&lt;Photo[] | null&gt;</td>
<td>Photo fetcher. Resolve promise with `null` to indicate end of stream.</td>
<td>
Photo fetcher. Resolve the promise with `null` to indicate the end of
the stream.
</td>
</tr>
<tr>
<td>photos</td>
<td>Photo[]</td>
<td>Initial photos (optional).</td>
</tr>
<tr>
<td>onClick</td>
<td>(&#123; photos, photo, index, event &#125;) => void</td>
<td>
Photo click callback. The `photos` parameter represents a flat array of
all fetched photos.
</td>
</tr>
<tr>
<td>retries</td>
<td>number</td>
Expand Down
35 changes: 25 additions & 10 deletions src/scroll/InfiniteScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Children, cloneElement, isValidElement, useCallback, useRef, useState }
import Offscreen from "./Offscreen";
import useEventCallback from "./useEventCallback";
import useIntersectionObserver from "./useIntersectionObserver";
import { CommonPhotoAlbumProps, Photo, RenderTrackProps } from "../types";
import { ClickHandlerProps, CommonPhotoAlbumProps, Photo, RenderTrackProps } from "../types";

enum Status {
IDLE,
Expand All @@ -14,11 +14,13 @@ enum Status {
}

/** InfiniteScroll component props. */
export type InfiniteScrollProps = {
export type InfiniteScrollProps<TPhoto extends Photo = Photo> = {
/** Photo fetcher. Resolve promise with `null` to indicate end of stream. */
fetch: (index: number) => Promise<Photo[] | null>;
fetch: (index: number) => Promise<TPhoto[] | null>;
/** Initial photos (optional). */
photos?: Photo[];
photos?: TPhoto[];
/** Click handler */
onClick?: ({ photos, photo, index, event }: ClickHandlerProps<TPhoto> & { photos: TPhoto[] }) => void;
/** Retry attempts. */
retries?: number;
/** Use a single photo album component (masonry layout). */
Expand All @@ -34,12 +36,13 @@ export type InfiniteScrollProps = {
/** Offscreen `IntersectionObserver` root margin setting. Default: `2000px` */
offscreenRootMargin?: string;
/** Photo album component. Must be the only child. */
children: React.ReactElement<Pick<CommonPhotoAlbumProps, "photos" | "render">>;
children: React.ReactElement<Pick<CommonPhotoAlbumProps<TPhoto>, "photos" | "render" | "onClick">>;
};

/** InfiniteScroll component. */
export default function InfiniteScroll({
export default function InfiniteScroll<TPhoto extends Photo>({
photos: initialPhotos,
onClick,
fetch,
retries = 0,
singleton,
Expand All @@ -49,9 +52,9 @@ export default function InfiniteScroll({
children,
fetchRootMargin = "800px",
offscreenRootMargin = "2000px",
}: InfiniteScrollProps) {
}: InfiniteScrollProps<TPhoto>) {
const [status, setStatus] = useState<Status>(Status.IDLE);
const [photos, setPhotos] = useState<Photo[][]>(() => (initialPhotos ? [initialPhotos] : []));
const [photos, setPhotos] = useState<TPhoto[][]>(() => (initialPhotos ? [initialPhotos] : []));

const { observe, unobserve } = useIntersectionObserver(fetchRootMargin);

Expand Down Expand Up @@ -120,11 +123,20 @@ export default function InfiniteScroll({
[observe, unobserve, handleFetch],
);

const photosArray = photos.flatMap((batch) => batch);

const handleClick = onClick
? ({ photo, event }: ClickHandlerProps<TPhoto>) => {
onClick({ photos: photosArray, index: photosArray.findIndex((item) => item === photo), photo, event });
}
: undefined;

return (
<>
{singleton
? cloneElement(children, {
photos: photos.flatMap((batch) => batch),
photos: photosArray,
onClick: handleClick,
render: {
...children.props.render,
// eslint-disable-next-line react/no-unstable-nested-components
Expand Down Expand Up @@ -153,7 +165,10 @@ export default function InfiniteScroll({
key={index}
rootMargin={offscreenRootMargin}
>
{cloneElement(children, { photos: batch })}
{cloneElement(children, {
photos: batch,
onClick: handleClick,
})}
</Offscreen>
))}

Expand Down
16 changes: 16 additions & 0 deletions test/scroll/InfiniteScroll.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,20 @@ describe("InfiniteScroll", () => {
await triggerIntersection();
expect(getPhotos().length).toBe(0);
});

it("supports the onClick callback", async () => {
const onClick = vi.fn();

const { getAllByRole } = render(
<InfiniteScroll singleton fetch={fetcher} photos={photos} onClick={onClick}>
<RowsPhotoAlbum photos={[]} />
</InfiniteScroll>,
);

act(() => {
getAllByRole("button")[0].click();
});

expect(onClick).toHaveBeenCalled();
});
});

0 comments on commit 3b35b74

Please sign in to comment.