This example is based on the list introduced in the basic example.
If you have not seen the basic example yet, please look over it first.
Add custom state for auto loading.
const state = {
$list1: createState({
loading: false, // show or hide loading message
lastId: 0 // last loaded item id
})
};
Add custom actions for auto loading.
const actions = {
$list1: createActions({
loadNextItems: (element) => (state, actions) => {
if (state.loading) return;
// show loading message
actions.startLoading();
setTimeout(() => {
// get current state
const items = state.items;
let nextId = state.lastId;
// add 10 dummy items
for (let i = 0; i < 10; i++) {
nextId++;
items.push({ id: nextId, name: `foo ${nextId}` });
}
// update state
actions.setItems(items);
actions.setLastId(nextId);
// hide loading message
actions.endLoading();
}, 1000)
},
setLastId: (lastId) => ({ lastId }),
startLoading: () => ({ loading: true }),
endLoading: () => ({ loading: false })
})
};
Create loading message component.
const Loading = () => (state) => {
if (! state.$list1.loading) return null;
return (
<div style={{
position: 'absolute',
left: '0',
bottom: '0',
width: '100%',
padding: '5px',
textAlign: 'center',
fontWeight: 'bold',
color: '#fff',
background: 'rgba(0, 0, 0, .7)'
}}>loading ...</div>
);
};
Add the loading component to the view.
const view = (state, actions) => (
<div>
<h1>Hyperapp InfiniteList auto loading example</h1>
<div style={{
height: '480px',
border: 'solid 1px #222',
position: 'relative'
}}>
<List
namespace="$list1"
itemHeight={80}
onReachBottom={actions.$list1.loadNextItems}
/>
<Loading />
</div>
</div>
);
Set position of the parent element to relative because the loading component position is absolute.
Set onReachBottom event action of the infinite list.
Place the loading component as a sibling element of the infinite list.
Set lastId of custom state at initialization.
const main = app(state, actions, view, document.getElementById('list'));
const items = [];
for (let i = 1; i <= 1000; i++) {
items.push({ id: i, name: `foo ${i}` });
}
main.$list1.setItems(items);
main.$list1.setLastId(1000);
Add animate scrolling library.
<script src="https://unpkg.com/animated-scroll-to/animated-scroll-to.js"></script>
Add a data-id attribute to each item element so that it can detect rendering of the item.
const List = createList(
({ id, name }) => (
<div key={id} data-id={id} style={{
width: '100%',
padding: '10px',
textAlign: 'center',
borderTop: (id === 1) ? 'none' : 'solid 1px #222'
}}>
<div>id: {id}</div>
<div>name: {name}</div>
</div>
)
);
Add scroll processing on adding items to loadNextItems action.
loadNextItems: (element) => (state, actions) => {
// show loading message
actions.startLoading();
setTimeout(() => {
// get current state
const items = state.items;
let nextId = state.lastId;
// add 10 dummy items
for (let i = 0; i < 20; i++) {
nextId++;
items.push({ id: nextId, name: `foo ${nextId}` });
}
// update state
actions.setItems(items);
actions.setLastId(nextId);
// hide loading message
actions.endLoading();
// wait for a new item to be rendered
const nextFirstId = state.lastId + 1;
let loopLimit = 10;
const timer = setInterval(() => {
if (document.querySelector(`[data-id="${nextFirstId}"]`)) {
// a new item was found on the list
animateScrollTo(element.scrollTop + 50, { element });
clearInterval(timer);
} else {
// no new items found on the list yet
loopLimit--;
if (loopLimit <= 0) clearInterval(timer);
}
}, 100);
}, 1000)
},