Skip to content

Instantly share code, notes, and snippets.

@emeraldsanto
Created August 18, 2020 01:05
Show Gist options
  • Save emeraldsanto/6f34d7e05f90f31dc25e9bc4807bcc6b to your computer and use it in GitHub Desktop.
Save emeraldsanto/6f34d7e05f90f31dc25e9bc4807bcc6b to your computer and use it in GitHub Desktop.
A wrapper over the base FlatList component with better pagination and automatic data-retrieval footers.
import { RefObject } from "react";
import { FlatList, FlatListProps } from "react-native";
import Success from "@assets/icons/success.svg";
import Warning from "@assets/icons/warning.svg";
import { ColorCode } from "@models/constants/ColorCode";
import { Row } from "@primitives/Flex/Row";
import { LocalizedText } from "@primitives/LocalizedText/LocalizedText";
import { useScrollToTop } from "@react-navigation/native";
import React, { FC, Fragment, useImperativeHandle, useRef, useState } from "react";
import { ActivityIndicator, FlatList, StyleSheet } from "react-native";
import { PaginatedListStyle } from "./PaginatedListStyle";
type BaseFlatListProps<T> = Omit<FlatListProps<T>, "onEndReached">;
export type DataRetrievalStatus = "available" | "unavailable" | "failed";
/**
* Function to be called once loading of paginated data was completed.
* @param {boolean} success A boolean indicating wether or not the fetching succeeded,
* will only increment page if `success` is true
*/
export type PaginationCallback = (status: DataRetrievalStatus) => void;
export interface PaginatedListProps<T> extends BaseFlatListProps<T> {
statusFooterDisabled?: boolean;
innerRef?: RefObject<PaginatedListRef>;
flatListRef?: (instance: FlatList<T> | null) => void;
onPageEndReached: (nextPage: number, done: PaginationCallback) => void;
}
export interface PaginatedListRef {
resetPageCounter: (count?: number) => void;
resetWorkingStatus: (busy?: boolean) => void;
resetDataRetrievalStatus: (status?: DataRetrievalStatus) => void;
}
export function PaginatedList<T>(props: PaginatedListProps<T>) {
const { statusFooterDisabled, innerRef, onPageEndReached, ListFooterComponent, flatListRef, ...rest } = props;
const [page, setPage] = useState<number>(0);
const [working, setWorking] = useState<boolean>(false);
const [currentStatus, setCurrentStatus] = useState<DataRetrievalStatus>("available");
const listRef = useRef<FlatList<T> | null>(null);
useScrollToTop(listRef);
useImperativeHandle(
innerRef,
() => ({
resetPageCounter,
resetWorkingStatus,
resetDataRetrievalStatus
})
);
function resetPageCounter(count: number = 0) {
resetDataRetrievalStatus("available");
resetWorkingStatus(false);
setPage(count);
}
function resetWorkingStatus(busy: boolean = false) {
setWorking(busy);
}
function resetDataRetrievalStatus(status: DataRetrievalStatus = "available") {
setCurrentStatus(status);
}
function handleRef(instance: FlatList<T> | null) {
listRef.current = instance;
flatListRef?.(instance);
}
function onEndReached(info: { distanceFromEnd: number }) {
if (working || currentStatus !== "available" || info.distanceFromEnd < 0) {
return;
}
setWorking(true);
onPageEndReached(page + 1, (status: DataRetrievalStatus) => {
setWorking(false);
setCurrentStatus(status);
if (status === "available") {
setPage(prev => prev + 1);
}
});
}
function renderFooter() {
if (statusFooterDisabled || props.data?.length === 0) {
return null;
}
else if (working) {
return (
<ActivityIndicator
size="large"
color={ColorCode.PRIMARY}
style={PaginatedListStyle.indicator}
/>
);
}
else if (currentStatus === "unavailable") {
return (
<CaughtUp />
);
}
else if (currentStatus === "failed") {
return (
<NetworkError />
);
}
return null;
}
return (
<FlatList
{...rest}
ref={handleRef}
removeClippedSubviews
onEndReached={onEndReached}
ListFooterComponent={
<Fragment>
{ListFooterComponent}
{renderFooter()}
</Fragment>
}
/>
);
}
const PaginatedListStyle = StyleSheet.create({
indicator: {
marginTop: Spacing.SMALL,
marginBottom: 25
},
footer: {
margin: Spacing.DEFAULT
},
icon: {
width: 28,
height: 28,
marginEnd: Spacing.SMALL
},
text: {
flex: 1,
fontFamily: FontFamily.ROBOTO.MEDIUM
}
});
const CaughtUp: FC = () => {
return (
<Row alignItems="center" justifyContent="center" style={PaginatedListStyle.footer}>
<Success {...PaginatedListStyle.icon} />
<LocalizedText style={PaginatedListStyle.text}>
allCaughtUp
</LocalizedText>
</Row>
)
}
const NetworkError: FC = () => {
return (
<Row alignItems="center" justifyContent="center" style={PaginatedListStyle.footer}>
<Warning {...PaginatedListStyle.icon} />
<LocalizedText style={PaginatedListStyle.text}>
errorLoadingContent
</LocalizedText>
</Row>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment