useFirestoreCollectionWithAtom: store next page availability state in atom

This commit is contained in:
Sidney Alcantara
2022-05-30 12:48:27 +10:00
parent 2a6deba5ce
commit fe285711e1
6 changed files with 69 additions and 53 deletions

View File

@@ -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, dont request another page
const tableLoadingMore = get(tableLoadingMoreAtom);
if (tableLoadingMore) return;
// If loading more or doesnt have next page, dont request another page
const tableNextPage = get(tableNextPageAtom);
if (tableNextPage.loading || !tableNextPage.available) return;
// Check that we havent 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.

View File

@@ -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 />

View File

@@ -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>

View File

@@ -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 atoms 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 were loading the next page. Uses same scope as `dataScope`. */
loadingMoreAtom?: PrimitiveAtom<boolean>;
/** Update this atom when were 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 were 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 doesnt fill the page, its 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

View File

@@ -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,
}
);

View File

@@ -36,6 +36,14 @@ export type UpdateCollectionDocFunction<T = TableRow> = (
*/
export type DeleteCollectionDocFunction = (path: string) => Promise<void>;
/**
* Store the next page state to know if its loading and if its available
*/
export type NextPageState = {
loading: boolean = false;
available: boolean = true;
};
/** Table settings stored in project settings */
export type TableSettings = {
id: string;