Skip to content

Commit

Permalink
Adds InfiniteScrollComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
ankeetmaini committed Mar 17, 2016
0 parents commit 1b7d93c
Show file tree
Hide file tree
Showing 16 changed files with 22,253 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

.DS_Store
.idea
node_modules
5 changes: 5 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/.gitignore
/.npmignore
/demos/
/server.js
/webpack.*
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# react-infinite-scroll
A component to make all your infinite scrolling woes go away with just 4.15 kB!

# install
```bash
npm install --save react-infinite-scroll-component
```

# demos
The code for demos is in the `demos/` directory. Open `lib/index.html` in your browser to see the demos in action.

# using
The `InfiniteScroll` component can be used in two ways.

- Without giving any height to your **scrollable** content. In which case the scroll will happen at `document.body` like *Facebook's* timeline scroll.
- If you want your **scrollable** content to have a definite height then your content will be scrollable with the height specified in the props.

```js
<InfiniteScroll
next={functionToLoadNextData}
hasMore={true}
loader={<h4>Loading...</h4>}>
{items} // your long array goes in here
</InfiniteScroll>
```
# props
name | type | description
-----|------|------------
**next** | function | a function which must be called after reaching the bottom. It must trigger some sort of action which fetches the next data. **The data is passed as `children` to the `InfiniteScroll` component and the data should contain previous items too.** e.g. *Initial data = [1, 2, 3]* and then next load of data should be *[1, 2, 3, 4, 5, 6]*.
**hasMore** | boolean | it tells the InfiniteScroll component on whether to call `next` function on reaching the bottom and shows an `endMessage` to the user
**children** | node (list) | the data items which you need to scroll.
**loader** | node | you can send a loader component to show while the component waits for the next load of data. e.g. `<h3>Loading...</h3>` or any fancy loader element
**scrollThreshold** | number | a threshold value after that the InfiniteScroll will call `next`. By default it's `0.8`. It means the `next` will be called when the user comes below 80% of the total height.
**endMessage** | node | this message is shown to the user when he has seen all the records which means he's at the bottom and `hasMore` is `false`
**style** | object | any style which you want to override
**height** | number | optional, give only if you want to have a fixed height scrolling content
**hasChildren** | bool | `children` is by default assumed to be of type array and it's length is used to determine if loader needs to be shown or not, if your `children` is not an array, specify this prop to tell if your items are 0 or more.
100 changes: 100 additions & 0 deletions app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, {Component, PropTypes} from 'react';
import debounce from './utils/debounce';

export default class InfiniteScroll extends Component {
constructor (props) {
super();
this.state = {
showLoader: false,
lastScrollTop: 0,
actionTriggered: false
};
this.onScrollListener = this.onScrollListener.bind(this);
this.debouncedOnScrollListener = debounce(this.onScrollListener, 150).bind(this);
}

componentDidMount () {
this.el = this.props.height ? this.refs.infScroll : window;
this.el.addEventListener('scroll', this.debouncedOnScrollListener);
}

componentWillUnMount () {
this.el.removeEventListener('scroll', this.debouncedOnScrollListener);
}

componentWillReceiveProps (props) {
// new data was sent in
this.setState({
showLoader: false,
actionTriggered: false
});
}

isElementAtBottom (target, scrollThreshold = 0.8) {
const scrolled = scrollThreshold * (target.scrollHeight - target.scrollTop);
const {clientHeight} = target;
return scrolled < clientHeight;
}

onScrollListener (event) {
let target = this.props.height
? event.target
: (document.documentElement.scrollTop ? document.documentElement : document.body);

// if user scrolls up, remove action trigger lock
if (target.scrollTop < this.state.lastScrollTop) {
this.setState({
actionTriggered: false,
lastScrollTop: target.scrollTop
});
return; // user's going up, we don't care
}

// return immediately if the action has already been triggered,
// prevents multiple triggers.
if (this.state.actionTriggered) return;

let atBottom = this.isElementAtBottom(target, this.props.scrollThreshold);

// call the `next` function in the props to trigger the next data fetch
if (atBottom && this.props.hasMore) {
this.props.next();
this.setState({actionTriggered: true, showLoader: true});
}
this.setState({lastScrollTop: target.scrollTop});
}

render () {
const style = {
height: this.props.height || 'auto',
overflow: 'auto'
};
const hasChildren = this.props.hasChildren || !!(this.props.children && this.props.children.length);
return (
<div className='infinite-scroll-component' ref='infScroll'
style={style}>
{this.props.children}
{!this.state.showLoader && !hasChildren && this.props.hasMore &&
this.props.loader}
{this.state.showLoader && this.props.loader}
{!this.props.hasMore && (
<p style={{textAlign: 'center'}}>
{this.props.endMessage || <b>Yay! You have seen it all</b>}
</p>
)}
</div>
);
}
}

InfiniteScroll.propTypes = {
next: PropTypes.func,
hasMore: PropTypes.bool,
children: PropTypes.node,
loader: PropTypes.node.isRequired,
scrollThreshold: PropTypes.number,
endMessage: PropTypes.node,
style: PropTypes.object,
height: PropTypes.number,
hasChildren: PropTypes.bool
};
14 changes: 14 additions & 0 deletions app/utils/debounce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default function debounce (func, wait) {
let timeout;
return function () {
const _this = this;
const args = arguments;

const later = function () {
timeout = null;
func.apply(_this, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
52 changes: 52 additions & 0 deletions demos/height.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import ReactDOM from 'react-dom';

import InfiniteScroll from '../app';

const divs = [
<div key={1} style={{height: 200, background: 'cornsilk'}}>Big div no 1</div>,
<div key={2} style={{height: 200, background: 'cornsilk'}}>Big div no 2</div>,
<div key={3} style={{height: 200, background: 'cornsilk'}}>Big div no 3</div>,
<div key={4} style={{height: 200, background: 'cornsilk'}}>Big div no 4</div>,
<div key={5} style={{height: 200, background: 'cornsilk'}}>Big div no 5</div>
];

const heightMessage = 'Infinite Scroll given fixed height of 300px in props';

export default class Height extends React.Component {
constructor () {
super();
this.state = {divs: divs};
this.generateDivs = this.generateDivs.bind(this);
}

generateDivs () {
let moreDivs = [];
let count = this.state.divs.length;
for (let i = 0; i < 10; i++) {
moreDivs.push(
<div key={'div' + count++} style={{background: 'cornsilk'}}>
Div no {count}
</div>
);
}
setTimeout(() => {
this.setState({divs: this.state.divs.concat(moreDivs)});
}, 500);
}

render () {
return (
<div>
<h3>{heightMessage}</h3>
<InfiniteScroll
next={this.generateDivs}
hasMore={true}
height={300}
loader={<h4>Loading...</h4>}>
{this.state.divs}
</InfiniteScroll>
</div>
);
}
}
27 changes: 27 additions & 0 deletions demos/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import ReactDOM from 'react-dom';

import Height from './height';
import NoHeight from './no-height';


const toggle = function () {
var mode = 'noHeight';
return () => {
if (mode === 'noHeight') {
mode = 'height';
ReactDOM.render(<Height/>, document.getElementById('app'));
} else {
mode = 'noHeight';
ReactDOM.render(<NoHeight/>, document.getElementById('app'));
}
};
}();

ReactDOM.render(
<button onClick={toggle}>Toggle between Height and No Height versions</button>,
document.getElementById('button')
);

// initial render
ReactDOM.render(<NoHeight/>, document.getElementById('app'));
53 changes: 53 additions & 0 deletions demos/no-height.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import ReactDOM from 'react-dom';

import InfiniteScroll from '../app';

const divs = [
<div key={1} style={{height: 200, background: 'cornsilk'}}>Big div no 1</div>,
<div key={2} style={{height: 200, background: 'cornsilk'}}>Big div no 2</div>,
<div key={3} style={{height: 200, background: 'cornsilk'}}>Big div no 3</div>,
<div key={4} style={{height: 200, background: 'cornsilk'}}>Big div no 4</div>,
<div key={5} style={{height: 200, background: 'cornsilk'}}>Big div no 5</div>
];

const noHeightMessage = 'No height given to InfiniteScroll, free scroll like Facebook';

export default class NoHeight extends React.Component {
constructor () {
super();
this.state = {
divs: divs
};
this.generateDivs = this.generateDivs.bind(this);
}

generateDivs () {
let moreDivs = [];
let count = this.state.divs.length;
for (let i = 0; i < 10; i++) {
moreDivs.push(
<div key={'div' + count++} style={{background: 'cornsilk'}}>
Div no {count}
</div>
);
}
setTimeout(() => {
this.setState({divs: this.state.divs.concat(moreDivs)});
}, 500);
}

render () {
return (
<div>
<h3>{noHeightMessage}</h3>
<InfiniteScroll
next={this.generateDivs}
hasMore={true}
loader={<h4>Loading...</h4>}>
{this.state.divs}
</InfiniteScroll>
</div>
);
}
}
18 changes: 18 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Infinite-Scroll</title>
<meta charset="utf-8">
<style>
#noHeight, #height {
float: left;
width: 50%;
}
</style>
</head>
<body>
<h1>Infinite Scroll Demo</h1>
<div id="button"></div>
<div id="app"></div>
</body>
</html>
Loading

0 comments on commit 1b7d93c

Please sign in to comment.