Skip to main content

Pagination

A set of presentational components for building pagination UI.

Import

import { Pagination } from "@dhua5922/react-native-kit";

Basic Example

Live Editor
function Example() {
  return (
    <Pagination>
      <Pagination.Item>
        <Pagination.First />
      </Pagination.Item>

      <Pagination.Item>
        <Pagination.Previous />
      </Pagination.Item>

      <Pagination.Item disabled>Page 1 of 20</Pagination.Item>

      <Pagination.Item>
        <Pagination.Next />
      </Pagination.Item>

      <Pagination.Item>
        <Pagination.Last />
      </Pagination.Item>
    </Pagination>
  );
}
Result
Loading...

Paginated Table Example

Live Editor
function PaginatedList() {
  const [currentPage, setCurrentPage] = useState(0);
  const pageSize = 3;
  const headers = ["Header 1", "Header 2"];
  const list = [
    ["Cell 1", "Cell 2"],
    ["Cell 3", "Cell 4"],
    ["Cell 5", "Cell 6"],
    ["Cell 7", "Cell 8"],
    ["Cell 9", "Cell 10"],
    ["Cell 11", "Cell 12"],
    ["Cell 13", "Cell 14"],
    ["Cell 15", "Cell 16"],
  ];
  const startIndex = currentPage * pageSize;
  const paginatedRows = list.slice(startIndex, startIndex + pageSize);

  return (
    <>
      <Table border hover striped>
        <Table.tr>
          {headers.map((header, index) => (
            <Table.th key={index}>{header}</Table.th>
          ))}
        </Table.tr>

        {paginatedRows.map((row, index) => (
          <Table.tr key={index} striped={index % 2 === 0}>
            {row.map((children, index) => (
              <Table.td key={index}>{children}</Table.td>
            ))}
          </Table.tr>
        ))}
      </Table>

      <Center>
        <Pagination style={{ marginTop: 20 }}>
          <Pagination.Item onPress={() => setCurrentPage(0)}>
            <Pagination.First />
          </Pagination.Item>

          <Pagination.Item
            onPress={() => setCurrentPage(Math.max(0, currentPage - 1))}
          >
            <Pagination.Previous />
          </Pagination.Item>

          <Pagination.Item disabled>
            <Text>
              Page {currentPage + 1} of {Math.ceil(list.length / pageSize)}
            </Text>
          </Pagination.Item>

          <Pagination.Item
            onPress={() =>
              setCurrentPage(
                Math.min(currentPage + 1, Math.floor(list.length / pageSize)),
              )
            }
          >
            <Pagination.Next />
          </Pagination.Item>

          <Pagination.Item
            onPress={() => setCurrentPage(Math.floor(list.length / pageSize))}
          >
            <Pagination.Last />
          </Pagination.Item>
        </Pagination>
      </Center>
    </>
  );
}
Result
Loading...

Paginated Table With Filtering Example

Live Editor
function PaginatedList() {
  const pageSize = 3;

  const headers = [
    {
      columnName: "First Name",
      propertyName: "firstName",
    },
    {
      columnName: "Last Name",
      propertyName: "lastName",
    },
  ];

  const list = [
    {
      firstName: "Matt",
      lastName: "Steve",
    },
    {
      firstName: "John",
      lastName: "Paul",
    },
    {
      firstName: "Kate",
      lastName: "Like",
    },
    {
      firstName: "Bart",
      lastName: "Homer",
    },
    {
      firstName: "Cain",
      lastName: "Kin",
    },
    {
      firstName: "Dylan",
      lastName: "Port",
    },
    {
      firstName: "Ava",
      lastName: "Alter",
    },
    {
      firstName: "Lex",
      lastName: "Altman",
    },
  ];
  const [filteredList, setFilteredList] = useState(list.slice());

  const [tableHeaders, setTableHeaders] = useState(
    headers.map((header) => ({
      ...header,
      showMenu: false,
      isSortAscending: false,
      isSortDescending: false,
      filterValue: "",
      isCheckingEqual: false,
      pickedEqualCheck: false,
      isCheckingContains: false,
      pickedContainsCheck: false,
    })),
  );

  const [currentPage, setCurrentPage] = useState(0);
  const startIndex = currentPage * pageSize;
  const paginatedRows = filteredList.slice(startIndex, startIndex + pageSize);

  const lastPageNumDisplay = Math.ceil(filteredList.length / pageSize || 1);
  const lastPageIndex = lastPageNumDisplay - 1;

  function modifyTableHeader(updatedTableHeader, index) {
    function differentChosenSortMapping() {
      const hasChoseDifferentSortProperty =
        updatedTableHeader.isSortAscending ||
        updatedTableHeader.isSortDescending;
      return (header) => ({
        ...header,
        isSortAscending: hasChoseDifferentSortProperty
          ? false
          : header.isSortAscending,
        isSortDescending: hasChoseDifferentSortProperty
          ? false
          : header.isSortDescending,
      });
    }

    setTableHeaders([
      ...tableHeaders.slice(0, index).map(differentChosenSortMapping()),
      { ...tableHeaders[index], ...updatedTableHeader },
      ...tableHeaders.slice(index + 1).map(differentChosenSortMapping()),
    ]);
  }

  function sort(firstValue, secondValue) {
    const stringA = firstValue.toLowerCase();
    const stringB = secondValue.toLowerCase();

    if (stringA < stringB) {
      return -1;
    } else if (stringA > stringB) {
      return 1;
    }

    return 0;
  }

  function filterList() {
    let newList = list
      .filter((person) => {
        return tableHeaders.every((header, index) => {
          const value = ("" + person[header.propertyName]).toLowerCase();

          if (header.pickedEqualCheck && header.isCheckingEqual) {
            return value === header.filterValue.toLowerCase();
          } else if (header.pickedContainsCheck && header.isCheckingContains) {
            return value.includes(header.filterValue.toLowerCase());
          }

          return true;
        });
      })
      .slice();

    const headerWithSort = tableHeaders.find(
      (header) => header.isSortAscending || header.isSortDescending,
    );
    headerWithSort &&
      newList.sort((personA, personB) =>
        headerWithSort.isSortAscending
          ? sort(
              personA[headerWithSort.propertyName],
              personB[headerWithSort.propertyName],
            )
          : sort(
              personB[headerWithSort.propertyName],
              personA[headerWithSort.propertyName],
            ),
      );

    return newList;
  }

  useEffect(() => {
    setFilteredList(filterList());
  }, [JSON.stringify(tableHeaders)]);

  return (
    <>
      <Table border hover striped>
        <Table.tr>
          {tableHeaders.map((header, columnIndex) => (
            <Table.th key={columnIndex}>
              <Menu
                showMenu={header.showMenu}
                onShowMenu={() =>
                  modifyTableHeader({ showMenu: true }, columnIndex)
                }
                onHideMenu={() =>
                  modifyTableHeader({ showMenu: false }, columnIndex)
                }
              >
                <Menu.Toggle
                >
                  <Row>
                    <Text bold>{header.columnName} </Text>
                    {(header.isCheckingEqual || header.isCheckingContains) && (
                      <FontAwesome5 name="filter" size={20} />
                    )}
                    {header.isSortAscending && "↑"}
                    {header.isSortDescending && "↓"}
                  </Row>
                </Menu.Toggle>
                <Menu.Content>
                  {[
                  {
                    label: "Clear Filter",
                    active: false,
                    onPress: () =>
                      modifyTableHeader(
                        {
                          ...header,
                          showMenu: false,
                          isSortAscending: false,
                          isSortDescending: false,
                          filterValue: "",
                          isCheckingEqual: false,
                          pickedEqualCheck: false,
                          isCheckingContains: false,
                          pickedContainsCheck: false,
                        },
                        columnIndex,
                      ),
                  },
                  {
                    label: "Sort Ascending",
                    active: header.isSortAscending,
                    onPress: () =>
                      modifyTableHeader(
                        {
                          isSortAscending: true,
                          isSortDescending: false,
                          showMenu: false,
                        },
                        columnIndex,
                      ),
                  },
                  {
                    label: "Sort Descending",
                    active: header.isSortDescending,
                    onPress: () =>
                      modifyTableHeader(
                        {
                          isSortAscending: false,
                          isSortDescending: true,
                          showMenu: false,
                        },
                        columnIndex,
                      ),
                  },
                  {
                    label: "Equals",
                    active: header.pickedEqualCheck,
                    onPress: () =>
                      modifyTableHeader(
                        {
                          pickedEqualCheck: true,
                          pickedContainsCheck: false,
                        },
                        columnIndex,
                      ),
                  },
                  {
                    label: "Contains",
                    active: header.pickedContainsCheck,
                    onPress: () =>
                      modifyTableHeader(
                        {
                          pickedEqualCheck: false,
                          pickedContainsCheck: true,
                        },
                        columnIndex,
                      ),
                  },
                  ].map(({ label, onPress, active }, index) => (
                    <Menu.Item key={index}>
                      <Pressable
                        onPress={() => {
                          onPress();
                          setCurrentPage(0);
                        }}
                      >
                        {active && "-> "}
                        {label}
                      </Pressable>
                    </Menu.Item>
                  ))}

                  <Menu.Item>
                  <Input.Search
                    placeholder="Type filter value here"
                    value={header.filterValue}
                    onChangeText={(text) => {
                      modifyTableHeader(
                        {
                          filterValue: text,
                        },
                        columnIndex,
                      );
                    }}
                  />
                  </Menu.Item>

                  <Menu.Item onPress={() => {}}>
                    <Row style={{ justifyContent: "flex-end" }}>
                      <Button
                        style={{ marginRight: 8 }}
                        onPress={() => {
                          setFilteredList(
                            filterList(
                              header.isSortAscending,
                              header.isSortDescending,
                              header.propertyName,
                            ),
                          );
                          modifyTableHeader(
                            {
                              isCheckingEqual: header.pickedEqualCheck,
                              isCheckingContains: header.pickedContainsCheck,
                              showMenu: false,
                            },
                            columnIndex,
                          );
                        }}
                      >
                        OK
                      </Button>

                      <Button
                        onPress={() => {
                          modifyTableHeader(
                            {
                              showMenu: false,
                            },
                            columnIndex,
                          );
                        }}
                      >
                        CANCEL
                      </Button>
                    </Row>
                  </Menu.Item>
                </Menu.Content>
              </Menu>
            </Table.th>
          ))}
        </Table.tr>

        {paginatedRows.map((person, index) => (
          <Table.tr key={index} striped={index % 2 === 0}>
            {tableHeaders.map((header, headerIndex) => (
              <Table.td key={headerIndex}>
                {person[header.propertyName]}
              </Table.td>
            ))}
          </Table.tr>
        ))}
      </Table>

      <Center>
        <Pagination style={{ marginTop: 20 }}>
          <Pagination.Item onPress={() => setCurrentPage(0)}>
            <Pagination.First />
          </Pagination.Item>

          <Pagination.Item
            onPress={() => setCurrentPage(Math.max(0, currentPage - 1))}
          >
            <Pagination.Previous />
          </Pagination.Item>

          <Pagination.Item disabled>
            <Text>
              Page {currentPage + 1} of {lastPageNumDisplay}
            </Text>
          </Pagination.Item>

          <Pagination.Item
            onPress={() =>
              setCurrentPage(Math.min(currentPage + 1, lastPageIndex))
            }
          >
            <Pagination.Next />
          </Pagination.Item>

          <Pagination.Item onPress={() => setCurrentPage(lastPageIndex)}>
            <Pagination.Last />
          </Pagination.Item>
        </Pagination>
      </Center>
    </>
  );
}
Result
Loading...

Props

note

Include all props from Row