Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented react-window-infinite-loader for Infinite Scrolling #25

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions example/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -98,6 +99,9 @@ const LinkContainer = () => (
<Menu.Item as={Link} to="/header">
Custom Styling
</Menu.Item>
<Menu.Item as={Link} to="/infinitescroll">
Infinite Scrolling
</Menu.Item>
<Menu.Item as={Link} to="/scroll">
Methods
</Menu.Item>
Expand Down Expand Up @@ -214,6 +218,13 @@ const App = () => {
<Route exact path="/props">
<Props />
</Route>
<Route exact path="/infinitescroll">
<Title title="Infinite Scrolling" />
<Wrapper>
<Example10 />
</Wrapper>
<Snippet code={Example10Code} />
</Route>
</Switch>
</Page>
</Application>
Expand Down
142 changes: 142 additions & 0 deletions example/src/examples/10-infinitescroll.js
Original file line number Diff line number Diff line change
@@ -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 (
<React.Fragment>
<Table
data={data}
columns={columns}
infiniteLoading={true}
hasNextPage={hasNextPage}
isNextPageLoading={isNexPageLoading}
loadNextPage={(start, stop) => loadNextPage(start, stop)}
minimumBatchSize={50}
tableHeight={400}
rowHeight={35}
borders={false}
/>
{isNexPageLoading && <div style={loaderStyle}><Loader active inline='centered'>Loading...</Loader></div>}
</React.Fragment>

);
};

// const Example10 = () => <Table data={testData} columns={columns} infiniteLoading={true} hasNextPage={true} />;

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 (
<Table
data={data}
columns={columns}
infiniteLoading={true}
hasNextPage={hasNextPage}
isNextPageLoading={isNexPageLoading}
loadNextPage={(start, stop) => loadNextPage(start, stop)}
minimumBatchSize={50}
tableHeight={400}
rowHeight={35}
borders={false}
/>
);
};
;
`;

export { Example10, Source };
47 changes: 47 additions & 0 deletions example/src/useStateWithCallback.js
Original file line number Diff line number Diff line change
@@ -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;
26 changes: 26 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CSSProperties, ElementType, FC, ReactNode } from "react";
import { ListOnItemsRenderedProps } from 'react-window';

declare module "*.svg" {
const content: string;
Expand Down Expand Up @@ -129,6 +130,7 @@ export interface ListProps {
itemKey?: KeyFunction;
subComponent?: ElementType<SubComponentProps>;
onRowClick?: ClickFunction;
onListItemsRendered?: (props: ListOnItemsRenderedProps) => any;
[key: string]: any;
}

Expand Down Expand Up @@ -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<any> | null;
/**
* When a column has `expander`, this component will be rendered under the row.
*/
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -93,6 +94,7 @@
"typescript": "^3.8.3"
},
"dependencies": {
"react-window-infinite-loader": "^1.0.5",
"react-window": "^1.8.5"
}
}
68 changes: 68 additions & 0 deletions src/InfiniteLoaderWrapper.tsx
Original file line number Diff line number Diff line change
@@ -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<any> | null;
children: (props: {onItemsRendered: OnItemsRendered, ref: Ref<any>}) => 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 (
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
minimumBatchSize={minimumBatchSize}
>
{({onItemsRendered, ref}) => (
children({
ref: ref,
onItemsRendered
})

)}
</InfiniteLoader>
);

}

InfiniteLoaderWrapper.defaultProps = {
hasNextPage: false,
isNextPageLoading: false,
loadNextPage: null,
minimumBatchSize: 10,
};

export default InfiniteLoaderWrapper;
3 changes: 3 additions & 0 deletions src/RowWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const RowWrapper = React.memo(({ data, index, ...rest }: Props) => {
const { rows, ...metaData } = data;
const row = rows[dataIndex];




return !row ? null : <Row row={row} index={dataIndex} {...rest} {...metaData} />;
}, areEqual);

Expand Down
Loading