Skip to main content

Displaying Large Lists

Pando offers multiple pagination patterns to display large lists of content in more manageable chunks so you can optimize the experience based on the scenario.

Manual Loading vs. Modern vs. Pagination Bar

Manual Loading

Manual loading is like infinite scrolling, but the user controls when the next set of content is loaded. This approach appends the next set of content to the end of the list, allowing the user to continue scrolling with minimal disruption.

This pattern works well in a "discovery" scenario when the user is browsing—not looking for anything specific—and aren't likely to need to revisit previously viewed content.

Ask yourself: "If I was a user, would I care about needing an organized approach for viewing old and new content?

Use this solution if you answered "no".

Modern

This approach displays only one set of content at a time while providing controls for the user to navigate forward or backward through the full list usually in the order of most recent data to oldest.

This is useful when navigating ordered content, such as comments or course listings and is what Github currently uses.

Ask yourself: "If I was a user, would I care about seeing the most recent information first and need a simple way to view older information?

Use this solution if you answered "yes".

Pagination Bar

The Pagination Bar expands on the Modern approach, offering more information and control to the user. This approach adds controls for the page size (number of items displayed) and indicates the size of the list, along with the user's position within that list.

The extra controls can be helpful when searching for content within larger, more complex lists, such as data tables, and works well when paired with more granular search/filtering controls.

Ask yourself: "If I was a user, would I care about needing to view more information and have full control over how it is displayed and navigated through?

Use this solution if you answered "yes".

Manual Loading for Simple Lists

For Manual Loading, we place the button at the end of the content and append the next set when it is pressed.

function ShowMoreButton() {
  // This is just a simple example. In a real-world scenario, you would
  // use the userReducer hook which is better suited for any non-primitive
  // state updates.
  const [content, setContent] = useState(() =>
    Array(5).fill('Most recent information'),
  )

  function handleFetchMoreData() {
    setContent((prev) => [
      ...prev.fill('Most recent information'),
      ...Array(5).fill('New content'),
    ])
  }

  return (
    <div>
      <ul>
        <For each={content}>{(item) => <li>{item}</li>}</For>
      </ul>
      <Button onClick={handleFetchMoreData}>Show more</Button>
    </div>
  )
}

Modern for Simple Lists

For the Modern approach, we use two buttons that replace the displayed content with the next set from the indicated direction. If there is no more content in that direction, the corresponding button should be disabled.

function PaginationButtons() {
  const [activePage, setActivePage] = useState(0)
  const content = useMemo(() => ['first page', 'second page', 'third page'], [])

  function loadNewerContent() {
    setActivePage((prev) => prev - 1)
  }

  function loadOlderContent() {
    setActivePage((prev) => prev + 1)
  }

  return (
    <div>
      {content[activePage]}
      <Flex align="center" justify="center" gap={16}>
        <Button disabled={activePage <= 0} onClick={loadNewerContent}>
          Newer
        </Button>
        <Button
          disabled={activePage >= content.length - 1}
          onClick={loadOlderContent}
        >
          Older
        </Button>
      </Flex>
    </div>
  )
}

Pagination Bar for Advanced Lists

For the Pagination Bar, we add page information combined with a Select component and the Modern approach appended to the end of the bar which shows disabled states based on the list data available for the user to navigate through.

function PaginationBar() {
  const [rowOption, setRowOption] = useState('5')
  const marginReset = useRef('8px')

  function handleSelectChange(e) {
    setRowOption(e.target.value)
  }

  return (
    <Flex align="center" justify="flex-end">
      <FlexItem grow={0}>
        <small
          style={{
            display: 'inline-block',
            marginTop: marginReset.current,
          }}
        >
          <strong>1-25</strong> of 12,345
        </small>
      </FlexItem>

      <FlexItem grow={0.5}>
        <FormControlProvider>
          <Label htmlFor="pagination" kind="hidden">
            Search by rows
          </Label>
          <Select onChange={handleSelectChange} pandoSize="m">
            <Option value="5">5 Rows</Option>
            <Option value="10">10 Rows</Option>
            <Option value="25">25 Rows</Option>
          </Select>
        </FormControlProvider>
      </FlexItem>

      <FlexItem>
        <Flex align="center" gap={6} style={{ marginTop: marginReset.current }}>
          <IconButton icon={CaretLeftIcon} disabled size="m" />
          <IconButton icon={CaretRightIcon} size="m" />
        </Flex>
      </FlexItem>
    </Flex>
  )
}