mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
useFirestoreCollectionWithAtom: store next page availability state in atom
This commit is contained in:
@@ -19,9 +19,9 @@ import {
|
||||
UpdateDocFunction,
|
||||
UpdateCollectionDocFunction,
|
||||
DeleteCollectionDocFunction,
|
||||
NextPageState,
|
||||
} from "@src/types/table";
|
||||
import { updateRowData } from "@src/utils/table";
|
||||
import { COLLECTION_PAGE_SIZE } from "@src/config/db";
|
||||
|
||||
/** Root atom from which others are derived */
|
||||
export const tableIdAtom = atom("");
|
||||
@@ -74,15 +74,11 @@ export const tableOrdersAtom = atom<TableOrder[]>([]);
|
||||
export const tablePageAtom = atom(
|
||||
0,
|
||||
(get, set, update: number | ((p: number) => number)) => {
|
||||
// If loading more, don’t request another page
|
||||
const tableLoadingMore = get(tableLoadingMoreAtom);
|
||||
if (tableLoadingMore) return;
|
||||
// If loading more or doesn’t have next page, don’t request another page
|
||||
const tableNextPage = get(tableNextPageAtom);
|
||||
if (tableNextPage.loading || !tableNextPage.available) return;
|
||||
|
||||
// Check that we haven’t loaded all rows
|
||||
const tableRowsDb = get(tableRowsDbAtom);
|
||||
const currentPage = get(tablePageAtom);
|
||||
if (tableRowsDb.length < (currentPage + 1) * COLLECTION_PAGE_SIZE) return;
|
||||
|
||||
set(
|
||||
tablePageAtom,
|
||||
typeof update === "number" ? update : update(currentPage)
|
||||
@@ -173,8 +169,11 @@ export const tableRowsAtom = atom<TableRow[]>((get) =>
|
||||
"_rowy_ref.path"
|
||||
)
|
||||
);
|
||||
/** Store loading more state for infinite scroll */
|
||||
export const tableLoadingMoreAtom = atom(false);
|
||||
/** Store next page state for infinite scroll */
|
||||
export const tableNextPageAtom = atom({
|
||||
loading: false,
|
||||
available: true,
|
||||
} as NextPageState);
|
||||
|
||||
/**
|
||||
* Store function to add or update row in db directly.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useRef, useMemo, useState } from "react";
|
||||
import { find, difference } from "lodash-es";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
tableSchemaAtom,
|
||||
tableColumnsOrderedAtom,
|
||||
tableRowsAtom,
|
||||
tableLoadingMoreAtom,
|
||||
tableNextPageAtom,
|
||||
tablePageAtom,
|
||||
updateColumnAtom,
|
||||
updateFieldAtom,
|
||||
@@ -62,8 +62,8 @@ export default function Table() {
|
||||
const [tableSchema] = useAtom(tableSchemaAtom, tableScope);
|
||||
const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope);
|
||||
const [tableRows] = useAtom(tableRowsAtom, tableScope);
|
||||
const [tableLoadingMore] = useAtom(tableLoadingMoreAtom, tableScope);
|
||||
const setTablePageAtom = useSetAtom(tablePageAtom, tableScope);
|
||||
const [tableNextPage] = useAtom(tableNextPageAtom, tableScope);
|
||||
const setTablePage = useSetAtom(tablePageAtom, tableScope);
|
||||
|
||||
const updateColumn = useSetAtom(updateColumnAtom, tableScope);
|
||||
const updateField = useSetAtom(updateFieldAtom, tableScope);
|
||||
@@ -163,15 +163,19 @@ export default function Table() {
|
||||
|
||||
// Gets more rows when scrolled down.
|
||||
// https://github.com/adazzle/react-data-grid/blob/ead05032da79d7e2b86e37cdb9af27f2a4d80b90/stories/demos/AllFeatures.tsx#L60
|
||||
const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
||||
const target = event.target as HTMLDivElement;
|
||||
const offset = 800;
|
||||
const isAtBottom =
|
||||
target.clientHeight + target.scrollTop >= target.scrollHeight - offset;
|
||||
if (!isAtBottom) return;
|
||||
// Call for the next page
|
||||
setTablePageAtom((p) => p + 1);
|
||||
};
|
||||
const handleScroll = useThrottledCallback(
|
||||
(event: React.UIEvent<HTMLDivElement>) => {
|
||||
const target = event.target as HTMLDivElement;
|
||||
const offset = 800;
|
||||
const isAtBottom =
|
||||
target.clientHeight + target.scrollTop >= target.scrollHeight - offset;
|
||||
if (!isAtBottom) return;
|
||||
console.log("Scroll");
|
||||
// Call for the next page
|
||||
setTablePage((p) => p + 1);
|
||||
},
|
||||
250
|
||||
);
|
||||
|
||||
const rowHeight = tableSchema.rowHeight ?? DEFAULT_ROW_HEIGHT;
|
||||
const handleResize = useDebouncedCallback(
|
||||
@@ -291,7 +295,7 @@ export default function Table() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tableLoadingMore && <LinearProgress />}
|
||||
{tableNextPage.loading && <LinearProgress />}
|
||||
</TableContainer>
|
||||
|
||||
{/* <ContextMenu />
|
||||
|
||||
@@ -6,10 +6,9 @@ import { Tooltip, Typography, TypographyProps } from "@mui/material";
|
||||
import {
|
||||
tableScope,
|
||||
tableRowsAtom,
|
||||
tableLoadingMoreAtom,
|
||||
tableNextPageAtom,
|
||||
tablePageAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
import { COLLECTION_PAGE_SIZE } from "@src/config/db";
|
||||
|
||||
const StatusText = forwardRef(function StatusText(
|
||||
props: TypographyProps,
|
||||
@@ -29,23 +28,21 @@ const StatusText = forwardRef(function StatusText(
|
||||
|
||||
function LoadedRowsStatus() {
|
||||
const [tableRows] = useAtom(tableRowsAtom, tableScope);
|
||||
const [tableLoadingMore] = useAtom(tableLoadingMoreAtom, tableScope);
|
||||
const [tableNextPage] = useAtom(tableNextPageAtom, tableScope);
|
||||
const [tablePage] = useAtom(tablePageAtom, tableScope);
|
||||
|
||||
if (tableLoadingMore) return <StatusText>Loading more…</StatusText>;
|
||||
|
||||
const allLoaded = tableRows.length < COLLECTION_PAGE_SIZE * (tablePage + 1);
|
||||
if (tableNextPage.loading) return <StatusText>Loading more…</StatusText>;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
allLoaded
|
||||
? "All rows have been loaded in this table"
|
||||
: "Scroll to the bottom to load more rows"
|
||||
tableNextPage.available
|
||||
? "Scroll to the bottom to load more rows"
|
||||
: "All rows have been loaded in this table"
|
||||
}
|
||||
>
|
||||
<StatusText>
|
||||
Loaded {allLoaded && "all "}
|
||||
Loaded {!tableNextPage.available && "all "}
|
||||
{tableRows.length} row{tableRows.length !== 1 && "s"}
|
||||
</StatusText>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import useMemoValue from "use-memo-value";
|
||||
import { useAtom, PrimitiveAtom, useSetAtom } from "jotai";
|
||||
import { useAtom, PrimitiveAtom, useSetAtom, SetStateAction } from "jotai";
|
||||
import { Scope } from "jotai/core/atom";
|
||||
import { set } from "lodash-es";
|
||||
import {
|
||||
@@ -27,6 +27,7 @@ import { globalScope } from "@src/atoms/globalScope";
|
||||
import {
|
||||
UpdateCollectionDocFunction,
|
||||
DeleteCollectionDocFunction,
|
||||
NextPageState,
|
||||
TableFilter,
|
||||
TableOrder,
|
||||
TableRow,
|
||||
@@ -56,8 +57,8 @@ interface IUseFirestoreCollectionWithAtomOptions<T> {
|
||||
updateDocAtom?: PrimitiveAtom<UpdateCollectionDocFunction<T> | undefined>;
|
||||
/** Set this atom’s value to a function that deletes a document in the collection. Must pass the full path. Uses same scope as `dataScope`. */
|
||||
deleteDocAtom?: PrimitiveAtom<DeleteCollectionDocFunction | undefined>;
|
||||
/** Update this atom when we’re loading the next page. Uses same scope as `dataScope`. */
|
||||
loadingMoreAtom?: PrimitiveAtom<boolean>;
|
||||
/** Update this atom when we’re loading the next page, and if there is a next page available. Uses same scope as `dataScope`. */
|
||||
nextPageAtom?: PrimitiveAtom<NextPageState>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +89,7 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
disableSuspense,
|
||||
updateDocAtom,
|
||||
deleteDocAtom,
|
||||
loadingMoreAtom,
|
||||
nextPageAtom,
|
||||
} = options || {};
|
||||
|
||||
const [firebaseDb] = useAtom(firebaseDbAtom, globalScope);
|
||||
@@ -106,10 +107,11 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
deleteDocAtom || (dataAtom as any),
|
||||
dataScope
|
||||
);
|
||||
const setLoadingMoreAtom = useSetAtom(
|
||||
loadingMoreAtom || (dataAtom as any),
|
||||
dataScope
|
||||
);
|
||||
const setNextPageAtom = useSetAtom<
|
||||
NextPageState,
|
||||
SetStateAction<NextPageState>,
|
||||
void
|
||||
>(nextPageAtom || (dataAtom as any), dataScope);
|
||||
|
||||
// Store if we’re at the last page to prevent a new query from being created
|
||||
const [isLastPage, setIsLastPage] = useState(false);
|
||||
@@ -142,9 +144,9 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
setDataAtom(new Promise(() => {}) as unknown as T[]);
|
||||
suspended = true;
|
||||
}
|
||||
// Set loadingMoreAtom if provided and getting the next page
|
||||
else if (memoizedQuery.page > 0 && loadingMoreAtom) {
|
||||
setLoadingMoreAtom(true);
|
||||
// Set nextPageAtom if provided and getting the next page
|
||||
else if (memoizedQuery.page > 0 && nextPageAtom) {
|
||||
setNextPageAtom((s) => ({ ...s, loading: true }));
|
||||
}
|
||||
|
||||
// Create a listener for the query
|
||||
@@ -160,8 +162,14 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
setDataAtom(docs);
|
||||
// If the snapshot doesn’t fill the page, it’s the last page
|
||||
if (docs.length < memoizedQuery.limit) setIsLastPage(true);
|
||||
// Mark loadingMore as done
|
||||
if (loadingMoreAtom) setLoadingMoreAtom(false);
|
||||
// Update nextPageAtom if provided
|
||||
if (nextPageAtom) {
|
||||
setNextPageAtom((s) => ({
|
||||
...s,
|
||||
loading: false,
|
||||
available: docs.length >= memoizedQuery.limit,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
if (onError) onError(error as FirestoreError);
|
||||
else handleError(error);
|
||||
@@ -173,7 +181,7 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
setDataAtom([]);
|
||||
suspended = false;
|
||||
}
|
||||
if (loadingMoreAtom) setLoadingMoreAtom(false);
|
||||
if (nextPageAtom) setNextPageAtom({ loading: false, available: true });
|
||||
if (onError) onError(error);
|
||||
else handleError(error);
|
||||
}
|
||||
@@ -182,7 +190,7 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
// When the listener will change, unsubscribe
|
||||
return () => {
|
||||
unsubscribe();
|
||||
if (loadingMoreAtom) setLoadingMoreAtom(false);
|
||||
if (nextPageAtom) setNextPageAtom({ loading: false, available: true });
|
||||
};
|
||||
}, [
|
||||
firebaseDb,
|
||||
@@ -191,8 +199,8 @@ export function useFirestoreCollectionWithAtom<T = TableRow>(
|
||||
setDataAtom,
|
||||
onError,
|
||||
handleError,
|
||||
loadingMoreAtom,
|
||||
setLoadingMoreAtom,
|
||||
nextPageAtom,
|
||||
setNextPageAtom,
|
||||
]);
|
||||
|
||||
// Create variable for validity of query to pass to useEffect dependencies
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
tableRowsDbAtom,
|
||||
_updateRowDbAtom,
|
||||
_deleteRowDbAtom,
|
||||
tableLoadingMoreAtom,
|
||||
tableNextPageAtom,
|
||||
auditChangeAtom,
|
||||
} from "@src/atoms/tableScope";
|
||||
|
||||
@@ -93,7 +93,7 @@ const TableSourceFirestore = memo(function TableSourceFirestore() {
|
||||
onError: handleErrorCallback,
|
||||
updateDocAtom: _updateRowDbAtom,
|
||||
deleteDocAtom: _deleteRowDbAtom,
|
||||
loadingMoreAtom: tableLoadingMoreAtom,
|
||||
nextPageAtom: tableNextPageAtom,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
8
src/types/table.d.ts
vendored
8
src/types/table.d.ts
vendored
@@ -36,6 +36,14 @@ export type UpdateCollectionDocFunction<T = TableRow> = (
|
||||
*/
|
||||
export type DeleteCollectionDocFunction = (path: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Store the next page state to know if it’s loading and if it’s available
|
||||
*/
|
||||
export type NextPageState = {
|
||||
loading: boolean = false;
|
||||
available: boolean = true;
|
||||
};
|
||||
|
||||
/** Table settings stored in project settings */
|
||||
export type TableSettings = {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user