@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
137 lines (106 loc) • 3.39 kB
text/typescript
import * as React from "react";
import * as R from "ramda";
import { FlatListProps, ViewToken } from "react-native";
type OnLoadFinished = (index: number) => () => void;
type SequentialLoaderOutput = {
isReadyToRender: (index: number) => boolean;
onLoadFinished: OnLoadFinished;
onLoadFailed: OnLoadFinished;
onViewableItemsChanged: FlatListProps<any>["onViewableItemsChanged"];
isRendered: (index: number) => boolean;
allLoaded: boolean;
};
type Action = {
index: number;
};
type ViewTokens = Array<ViewToken>;
type RenderingState = Array<boolean>;
const setTrueByIndex = (index, state) => R.set(R.lensIndex(index), true)(state);
const isInViewport = (index, viewableItems) =>
R.any(R.propEq("index", index))(viewableItems);
const reducer = (state: RenderingState, action: Action): RenderingState => {
if (state?.[action.index]) {
return state;
}
return setTrueByIndex(action.index, state);
};
const getIndexOfFirstNotRendered = R.findIndex(R.equals(false));
const isTrue = (value) => value === true;
export const useSequentialLoader = (
useSequentialLoading: boolean,
numberOfItems: number
): SequentialLoaderOutput => {
const [viewableItems, setViewableItems] = React.useState<ViewTokens>([]);
const [componentsRenderingState, dispatch] = React.useReducer(
reducer,
new Array(numberOfItems).fill(false)
);
const isRendered = React.useCallback(
(index: number): boolean => {
if (!useSequentialLoading || index === 0) {
return true;
}
return componentsRenderingState[index];
},
[componentsRenderingState]
);
const isReadyToRender = React.useCallback(
(index: number): boolean => {
if (!useSequentialLoading || index === 0) {
return true;
}
if (R.isEmpty(viewableItems)) {
return false;
}
const isRendered = componentsRenderingState[index];
if (isRendered) {
return true;
}
const previousLoaded =
index === 0 ? true : componentsRenderingState[index - 1];
const isNthNotRendered = (nth: number) =>
index ===
getIndexOfFirstNotRendered(componentsRenderingState) - 1 + nth;
const isNthComponentOutsideViewport = (nth: number) =>
index === R.last(viewableItems)?.index + nth;
const inViewport = isInViewport(index, viewableItems);
if (isNthNotRendered(1) && previousLoaded) {
return (
inViewport ||
isNthComponentOutsideViewport(1) ||
isNthComponentOutsideViewport(2) ||
isNthComponentOutsideViewport(3)
);
}
return inViewport && previousLoaded;
},
[viewableItems, componentsRenderingState]
);
const onLoadFinished = React.useCallback(
(index: number) => () => {
if (useSequentialLoading) {
dispatch({ index });
}
},
[]
);
const onViewableItemsChanged = React.useCallback(({ viewableItems }) => {
if (useSequentialLoading) {
setViewableItems(viewableItems);
}
}, []);
const allLoaded = React.useMemo((): boolean => {
if (useSequentialLoading) {
return R.all(isTrue, componentsRenderingState);
}
return true;
}, [componentsRenderingState]);
return {
isReadyToRender,
onLoadFinished,
onViewableItemsChanged,
isRendered,
allLoaded,
onLoadFailed: onLoadFinished,
};
};