mirror of
https://github.com/outline/outline.git
synced 2026-08-03 13:27:25 +03:00
* chore: Exhaustive deps turned to error * Refactor useDragResize * Refactor Lightbox to explicit state machine * feedback * ci
113 lines
3.0 KiB
TypeScript
113 lines
3.0 KiB
TypeScript
import { uniqBy } from "es-toolkit/compat";
|
|
import { useState, useEffect, useCallback } from "react";
|
|
import type { PaginationParams } from "~/types";
|
|
import useRequest from "./useRequest";
|
|
|
|
type RequestResponse<T> = {
|
|
/** The return value of the paginated request function. */
|
|
data: T[] | undefined;
|
|
/** The request error, if any. */
|
|
error: unknown;
|
|
/** Whether the request is currently in progress. */
|
|
loading: boolean;
|
|
/** Function to trigger next page request. */
|
|
next: () => void;
|
|
/** Page number */
|
|
page: number;
|
|
/** Offset */
|
|
offset: number;
|
|
/** Marks the end of pagination */
|
|
end: boolean;
|
|
};
|
|
|
|
const INITIAL_OFFSET = 0;
|
|
const DEFAULT_LIMIT = 10;
|
|
|
|
/**
|
|
* A hook to make paginated API request and track its state within a component.
|
|
*
|
|
* @param requestFn The function to call to make the request, it should return a promise.
|
|
* @param params Pagination params(limit, offset etc) to be passed to requestFn.
|
|
* @returns
|
|
*/
|
|
export default function usePaginatedRequest<T = unknown>(
|
|
requestFn: (params?: PaginationParams) => Promise<T[]>,
|
|
params: PaginationParams = {}
|
|
): RequestResponse<T> {
|
|
const [data, setData] = useState<T[]>();
|
|
const [offset, setOffset] = useState(INITIAL_OFFSET);
|
|
const [page, setPage] = useState(0);
|
|
const [end, setEnd] = useState(false);
|
|
const displayLimit = params.limit || DEFAULT_LIMIT;
|
|
const fetchLimit = displayLimit + 1;
|
|
const [paginatedReq, setPaginatedReq] = useState(
|
|
() => () =>
|
|
requestFn({
|
|
...params,
|
|
offset: 0,
|
|
limit: fetchLimit,
|
|
})
|
|
);
|
|
|
|
const {
|
|
data: response,
|
|
error,
|
|
loading,
|
|
request,
|
|
} = useRequest<T[]>(paginatedReq);
|
|
|
|
useEffect(() => {
|
|
void request();
|
|
}, [request]);
|
|
|
|
useEffect(() => {
|
|
if (response && !loading) {
|
|
setData((prev) =>
|
|
uniqBy((prev ?? []).concat(response.slice(0, displayLimit)), "id")
|
|
);
|
|
setPage((prev) => prev + 1);
|
|
setEnd(response.length <= displayLimit);
|
|
}
|
|
}, [response, displayLimit, loading]);
|
|
|
|
useEffect(() => {
|
|
if (offset) {
|
|
setPaginatedReq(
|
|
() => () =>
|
|
requestFn({
|
|
...params,
|
|
offset,
|
|
limit: fetchLimit,
|
|
})
|
|
);
|
|
}
|
|
// `params` is intentionally omitted, callers pass an object literal which
|
|
// changes identity on every render.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [offset, fetchLimit, requestFn]);
|
|
|
|
const next = useCallback(() => {
|
|
setOffset((prev) => prev + displayLimit);
|
|
}, [displayLimit]);
|
|
|
|
useEffect(() => {
|
|
setEnd(false);
|
|
setData(undefined);
|
|
setPage(0);
|
|
setOffset(0);
|
|
setPaginatedReq(
|
|
() => () =>
|
|
requestFn({
|
|
...params,
|
|
offset: 0,
|
|
limit: fetchLimit,
|
|
})
|
|
);
|
|
// `params` and `fetchLimit` are intentionally omitted, resetting pagination
|
|
// should only be driven by the request function changing.
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [requestFn]);
|
|
|
|
return { data, next, loading, error, page, offset, end };
|
|
}
|