diff --git a/example/src/App.js b/example/src/App.js
index 702a781..c1d80f0 100644
--- a/example/src/App.js
+++ b/example/src/App.js
@@ -12,6 +12,7 @@ import { Example6, Source as Example6Code } from "./examples/06-expanded";
import { Example7, Source as Example7Code } from "./examples/07-controlled";
import { Example8, Source as Example8Code } from "./examples/08-header";
import { Example9, Source as Example9Code } from "./examples/09-scroll";
+import { Example10, Source as Example10Code } from "./examples/10-infinitescroll";
import Navigation from "./Navigation";
import Props from "./Props";
import { Snippet } from "./shared/Snippet";
@@ -98,6 +99,9 @@ const LinkContainer = () => (
Custom Styling
+
+ Infinite Scrolling
+
Methods
@@ -214,6 +218,13 @@ const App = () => {
+
+
+
+
+
+
+
diff --git a/example/src/examples/10-infinitescroll.js b/example/src/examples/10-infinitescroll.js
new file mode 100644
index 0000000..7e7913c
--- /dev/null
+++ b/example/src/examples/10-infinitescroll.js
@@ -0,0 +1,142 @@
+import React, { useState} from "react";
+import { Table } from "react-fluid-table";
+import { testData } from "../data";
+import _ from "lodash";
+import { useStateWithCallbackLazy } from '../useStateWithCallback'
+import { Loader } from 'semantic-ui-react'
+
+const columns = [
+ {
+ key: "id",
+ header: "ID",
+ width: 50,
+ sortable: true,
+ },
+ {
+ key: "firstName",
+ header: "First",
+ sortable: true,
+ width: 120
+ },
+ {
+ key: "lastName",
+ header: "Last",
+ sortable: true,
+ width: 120
+ },
+ {
+ key: "email",
+ header: "Email",
+ sortable: true,
+ width: 250
+ }
+];
+
+const loaderStyle = { width: "100%", padding: "10px" };
+
+const Example10 = () => {
+ const [data, setData] = useState([]);
+ const [hasNextPage, setHasNextPage] = useState(true);
+ const [isNexPageLoading, setIsNextPageLoading] = useStateWithCallbackLazy(false);
+
+
+ const loadNextPage = (...args) => {
+ setIsNextPageLoading(true, () => {
+ setTimeout(() => {
+ setHasNextPage(data.length < testData.length)
+ setIsNextPageLoading(false)
+ setData(() => {
+ let newData = testData.slice(args[0], args[1])
+ //console.log(newData, args[0], args[1], "Info")
+ return data.concat(newData)
+ })
+
+ }, 1000);
+ })
+ }
+
+ return (
+
+ loadNextPage(start, stop)}
+ minimumBatchSize={50}
+ tableHeight={400}
+ rowHeight={35}
+ borders={false}
+ />
+ {isNexPageLoading && Loading...
}
+
+
+ );
+};
+
+// const Example10 = () => ;
+
+const Source = `
+
+import React, { useState} from "react";
+import { Table } from "react-fluid-table";
+import _ from "lodash";
+import { useStateWithCallbackLazy } from '../useStateWithCallback'
+import { Loader } from 'semantic-ui-react'
+
+const testData = _.range(3000).map(i => ({
+ id: i + 1,
+ firstName: faker.name.firstName(),
+ lastName: faker.name.lastName(),
+ email: faker.internet.email()
+}));
+
+const columns = [
+ { key: "id", header: "ID", width: 50 },
+ { key: "firstName", header: "First", width: 120 },
+ { key: "lastName", header: "Last", width: 120 },
+ { key: "email", header: "Email", width: 250 }
+];
+
+const loaderStyle = { width: "100%", padding: "10px" };
+
+const Example = () => {
+ const [data, setData] = useState([]);
+ const [hasNextPage, setHasNextPage] = useState(true);
+ const [isNexPageLoading, setIsNextPageLoading] = useStateWithCallbackLazy(false);
+
+
+ const loadNextPage = (...args) => {
+ setIsNextPageLoading(true, () => {
+ setTimeout(() => {
+ setHasNextPage(data.length < testData.length)
+ setIsNextPageLoading(false)
+ setData(() => {
+ let newData = testData.slice(args[0], args[1])
+ return data.concat(newData)
+ })
+
+ }, 1000);
+ })
+ }
+
+ return (
+ loadNextPage(start, stop)}
+ minimumBatchSize={50}
+ tableHeight={400}
+ rowHeight={35}
+ borders={false}
+ />
+ );
+ };
+;
+`;
+
+export { Example10, Source };
diff --git a/example/src/useStateWithCallback.js b/example/src/useStateWithCallback.js
new file mode 100644
index 0000000..031e147
--- /dev/null
+++ b/example/src/useStateWithCallback.js
@@ -0,0 +1,47 @@
+// Extracted from https://github.com/the-road-to-learn-react/use-state-with-callback
+// Thanks to ROBIN WIERUCH
+
+
+import { useState, useEffect, useLayoutEffect, useRef } from 'react';
+
+const useStateWithCallback = (initialState, callback) => {
+ const [state, setState] = useState(initialState);
+
+ useEffect(() => callback(state), [state, callback]);
+
+ return [state, setState];
+};
+
+const useStateWithCallbackInstant = (initialState, callback) => {
+ const [state, setState] = useState(initialState);
+
+ useLayoutEffect(() => callback(state), [state, callback]);
+
+ return [state, setState];
+ };
+
+const useStateWithCallbackLazy = initialValue => {
+ const callbackRef = useRef(null);
+
+ const [value, setValue] = useState(initialValue);
+
+ useEffect(() => {
+ if (callbackRef.current) {
+ callbackRef.current(value);
+
+ callbackRef.current = null;
+ }
+ }, [value]);
+
+ const setValueWithCallback = (newValue, callback) => {
+ callbackRef.current = callback;
+
+ return setValue(newValue);
+ };
+
+ return [value, setValueWithCallback];
+ };
+
+ export { useStateWithCallbackInstant, useStateWithCallbackLazy };
+
+export default useStateWithCallback;
\ No newline at end of file
diff --git a/index.d.ts b/index.d.ts
index 28fd885..929e5ee 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,4 +1,5 @@
import { CSSProperties, ElementType, FC, ReactNode } from "react";
+import { ListOnItemsRenderedProps } from 'react-window';
declare module "*.svg" {
const content: string;
@@ -129,6 +130,7 @@ export interface ListProps {
itemKey?: KeyFunction;
subComponent?: ElementType;
onRowClick?: ClickFunction;
+ onListItemsRendered?: (props: ListOnItemsRenderedProps) => any;
[key: string]: any;
}
@@ -199,6 +201,30 @@ export interface TableProps {
* a function that takes the index of the row and returns an object.
*/
rowStyle?: CSSProperties | ((index: number) => CSSProperties);
+
+ /**
+ * Enable or disable infinite loading. Default: `false`.
+ */
+ infiniteLoading?: boolean;
+ /**
+ * Are there more items to load?. Default: `false`.
+ * (This information comes from the most recent API request.)
+ */
+ hasNextPage?: boolean;
+ /**
+ * Are we currently loading a page of items?. Default: `false`.
+ * (This may be an in-flight flag in your Redux store or in-memory.)
+ */
+ isNextPageLoading?: boolean;
+ /**
+ * Minimum number of rows to be loaded at a time; . . Default: `10`.
+ * (This property can be used to batch requests to reduce HTTP requests.)
+ */
+ minimumBatchSize?: number;
+ /**
+ * Callback function responsible for loading the next page of items. Default: `undefined`.
+ */
+ loadNextPage?: (startIndex: number, stopIndex: number) => Promise | null;
/**
* When a column has `expander`, this component will be rendered under the row.
*/
diff --git a/package.json b/package.json
index e196424..0dc3579 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "react-fluid-table",
+ "name": "@carlospence/react-fluid-table",
"version": "0.4.2",
"description": "A React table inspired by react-window",
"author": "Mckervin Ceme ",
@@ -27,7 +27,7 @@
"scripts": {
"test": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
- "build": "rm -rf dist ||: && rollup -c --environment BUILD:production",
+ "build": "rm -rf dist && rollup -c --environment BUILD:production",
"start": "rollup -c -w --environment BUILD:development",
"prepare": "yarn run build",
"predeploy": "cd example && yarn install && yarn run build",
@@ -65,6 +65,7 @@
"@svgr/rollup": "^5.3.1",
"@testing-library/react-hooks": "^3.2.1",
"@types/react-window": "^1.8.1",
+ "@types/react-window-infinite-loader": "^1.0.3",
"babel-eslint": "^10.1.0",
"cross-env": "^7.0.2",
"eslint": "6.6.0",
@@ -93,6 +94,7 @@
"typescript": "^3.8.3"
},
"dependencies": {
+ "react-window-infinite-loader": "^1.0.5",
"react-window": "^1.8.5"
}
}
diff --git a/src/InfiniteLoaderWrapper.tsx b/src/InfiniteLoaderWrapper.tsx
new file mode 100644
index 0000000..3023b88
--- /dev/null
+++ b/src/InfiniteLoaderWrapper.tsx
@@ -0,0 +1,68 @@
+import React, { ReactNode, Ref } from "react";
+import InfiniteLoader from 'react-window-infinite-loader';
+
+import { ListOnItemsRenderedProps } from 'react-window';
+
+type OnItemsRendered = (props: ListOnItemsRenderedProps) => any;
+export interface Generic {
+ [key: string]: any;
+ }
+interface InfiniteLoaderWrapperProps {
+ hasNextPage?: boolean;
+ isNextPageLoading?: boolean;
+ minimumBatchSize?: number,
+ data: Generic[];
+ loadNextPage?: (startIndex: number, stopIndex: number) => Promise | null;
+ children: (props: {onItemsRendered: OnItemsRendered, ref: Ref}) => ReactNode;
+}
+
+
+
+/**
+ * Implementing react-window-infinite-loader ExampleWrapper.
+ */
+
+
+const InfiniteLoaderWrapper = ({ hasNextPage, isNextPageLoading, minimumBatchSize, data, loadNextPage, children }: InfiniteLoaderWrapperProps) => {
+
+ // If there are more items to be loaded then add an extra row to hold a loading indicator.
+ const itemCount = hasNextPage ? minimumBatchSize ? data.length + minimumBatchSize : data.length + 10 : data.length;
+
+ // Only load 1 page of items at a time.
+ // Pass an empty callback to InfiniteLoader in case it asks us to load more than once.
+ const loadMoreItems = isNextPageLoading ? () => null : loadNextPage == undefined ? () => null : loadNextPage
+
+ // Every row is loaded except for our loading indicator row.
+ const isItemLoaded = (index : number) => {
+ return !hasNextPage || index < data.length;
+ }
+
+
+
+ return (
+
+ {({onItemsRendered, ref}) => (
+ children({
+ ref: ref,
+ onItemsRendered
+ })
+
+ )}
+
+ );
+
+}
+
+InfiniteLoaderWrapper.defaultProps = {
+ hasNextPage: false,
+ isNextPageLoading: false,
+ loadNextPage: null,
+ minimumBatchSize: 10,
+};
+
+export default InfiniteLoaderWrapper;
\ No newline at end of file
diff --git a/src/RowWrapper.tsx b/src/RowWrapper.tsx
index a328992..7190ae4 100644
--- a/src/RowWrapper.tsx
+++ b/src/RowWrapper.tsx
@@ -13,6 +13,9 @@ const RowWrapper = React.memo(({ data, index, ...rest }: Props) => {
const { rows, ...metaData } = data;
const row = rows[dataIndex];
+
+
+
return !row ? null :
;
}, areEqual);
diff --git a/src/Table.tsx b/src/Table.tsx
index a9570d6..8ea5047 100644
--- a/src/Table.tsx
+++ b/src/Table.tsx
@@ -15,6 +15,7 @@ import { DEFAULT_HEADER_HEIGHT, DEFAULT_ROW_HEIGHT, NO_NODE } from "./constants"
import Header from "./Header";
import NumberTree from "./NumberTree";
import RowWrapper from "./RowWrapper";
+import InfiniteLoaderWrapper from "./InfiniteLoaderWrapper";
import { TableContext, TableContextProvider } from "./TableContext";
import TableWrapper from "./TableWrapper";
import {
@@ -36,6 +37,7 @@ interface Data {
*/
const ListComponent = forwardRef(
({ data, width, height, itemKey, rowHeight, className, ...rest }: ListProps, ref) => {
+
// hooks
const timeoutRef = useRef(0);
const prevRef = useRef(width);
@@ -224,6 +226,7 @@ const ListComponent = forwardRef(
listRef.current.scrollToItem(index, align)
}));
+
return (
{
if (!index) {
const header = findHeaderByUuid(uuid);
@@ -250,7 +254,10 @@ const ListComponent = forwardRef(
return calculateHeight(index - 1);
}}
- onItemsRendered={() => {
+ onItemsRendered={(info) => {
+ if (rest.onListItemsRendered) {
+ rest.onListItemsRendered(info);
+ }
// find median height of rows if no rowHeight provided
if (rowHeight || !tableRef.current) {
return;
@@ -302,6 +309,7 @@ const Table = forwardRef(
tableWidth,
tableStyle,
headerStyle,
+ infiniteLoading,
...rest
}: TableProps,
ref
@@ -310,6 +318,7 @@ const Table = forwardRef(
const disableHeight = tableHeight !== undefined;
const disableWidth = tableWidth !== undefined;
const [uuid] = useState(`${id || "data-table"}-${randomString()}`);
+ //console.log(rest.data)
return (
- {typeof tableHeight === "number" && typeof tableWidth === "number" ? (
+
+
+{typeof tableHeight === "number" && typeof tableWidth === "number" ? infiniteLoading ?
+ (
+ {({onItemsRendered, ref}) => ()}
+ ) : (
- ) : (
+ ) : infiniteLoading ?
+ (
+
+ {({ height, width }) => (
+
+ {({onItemsRendered, ref}) => ( )}
+
+ )}
+
+ ) :
+ (
+
{({ height, width }) => (