How to Implementation

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.

Step 1

Add custom state for auto loading.


const state = {
  $list1: createState({
    loading: false,  // show or hide loading message 
    lastId: 0        // last loaded item id
  })
};
        

Step 2

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

Step 3

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

Step 4

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.

Step 5

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);
        

Enhancement: When new items are added, scroll the infinite list a little.

Step 1

Add animate scrolling library.


<script src="https://unpkg.com/animated-scroll-to/animated-scroll-to.js"></script>
        

Step 2

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>
  )
);
        

Step 3

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