UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

137 lines (106 loc) 3.39 kB
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, }; };