@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
164 lines (130 loc) • 4.97 kB
text/typescript
import { isNil, complement, compose, map, min, prop, take, uniq } from "ramda";
import { useDispatch } from "react-redux";
import * as React from "react";
import { useZappPipesFeeds } from "@applicaster/zapp-react-native-redux/hooks/useZappPipesFeeds";
import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
import { isNilOrEmpty } from "../../reactUtils/helpers";
import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
import {
getInflatedDataSourceUrl,
getSearchContext,
} from "@applicaster/zapp-react-native-utils/reactHooks";
import { useScreenContext } from "../screen/useScreenContext";
type Options = {
initialBatchSize?: number;
riverId?: string;
isNestedScreen?: boolean;
};
const DEFAULT_BATCH_SIZE = 5;
const dataOf: (feed: ZappPipesData) => ZappPipesData["data"] = prop("data");
const feedIsLoading: (feed: ZappPipesData) => ZappPipesData["loading"] =
prop("loading");
const getErrorProp: (feed: ZappPipesData) => ZappPipesData["error"] =
prop("error");
const feedHasData: (feed: ZappPipesData) => boolean = compose(
complement(isNil),
dataOf
);
type ZappDataSourceSource = ZappDataSource["source"];
type ZappPipesCollection = Record<string, ZappPipesData>;
const checkFeedIsReady = (feed: ZappPipesData): boolean =>
feed && !feedIsLoading(feed) && (feedHasData(feed) || !!getErrorProp(feed));
const filterEmptyData = (data) => {
if (isNilOrEmpty(data) || isNilOrEmpty(data?.source)) return false;
return true;
};
const getData = (rawData) =>
rawData.component_type === "gallery-qb"
? rawData.ui_components[0].data
: rawData.data;
const extractData = compose(uniq, map(getData));
export const allFeedsIsReady = (
feeds: ZappPipesCollection,
urls: (ZappDataSourceSource | null)[]
): boolean =>
urls.every((url) =>
isNil(url) ? true : url && feeds[url] ? checkFeedIsReady(feeds[url]) : false
);
export const useBatchLoading = (
componentsToRender: { data?: ZappDataSource; component_type: string }[],
options: Options
) => {
const dispatch = useDispatch();
const { screen: screenContext, entry: entryContext } = useScreenContext();
const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);
// if first component is gallery-qb, take only one component for initial load
const takeSize =
componentsToRender?.[0]?.component_type === "gallery-qb"
? 1
: min(
options.initialBatchSize ?? DEFAULT_BATCH_SIZE,
componentsToRender.length
);
const takeBatchSize = React.useCallback(take(takeSize), [takeSize]);
// Prepare inflated url for datasource
const getUrl = React.useCallback(
(data) => {
const mappedFeed = data.mapping
? getInflatedDataSourceUrl({
source: data.source,
contexts: {
entry: entryContext,
screen: screenContext,
search: getSearchContext(searchContext, data.mapping),
},
mapping: data.mapping,
})
: data.source;
return mappedFeed;
},
[entryContext, screenContext, searchContext]
);
// components for initial load
const batchComponents = takeBatchSize(componentsToRender);
// contains datasources from batch components loading
const feedData: ZappDataSource[] = React.useMemo(
() => extractData(batchComponents).filter(filterEmptyData),
[batchComponents]
);
// contains inflated urls from batch components loading
const feedUrls: ZappDataSourceSource[] = React.useMemo(
() =>
feedData.map((data) => {
const mappedFeedUrl = getUrl(data);
return mappedFeedUrl;
}),
[]
);
const feeds = useZappPipesFeeds(feedUrls);
// dispatch loadPipesData for each feed that is not loaded
const runBatchLoading = React.useCallback(() => {
batchComponents.forEach((rawData: any) => {
// 1. get data (dataOf)
const data = getData(rawData);
// 2. filter out empty data filterEmptyData
if (!filterEmptyData(data)) return false;
// 3. filter out data that is already loaded (feeds[data.source]) & !feedHasData(feed) && !feedIsLoading(feed);
const feed = feeds[data?.source];
if (!feed || (!feedHasData(feed) && !feedIsLoading(feed))) {
const mappedFeedUrl = getUrl(data);
if (mappedFeedUrl) {
// 4. load data
return dispatch(
loadPipesData(mappedFeedUrl, { riverId: options.riverId })
);
}
}
});
}, [feedUrls]);
React.useEffect(() => {
runBatchLoading();
}, []);
React.useEffect(() => {
// check if all feeds are ready and set hasEverBeenReady to true
if (allFeedsIsReady(feeds, feedUrls) && !hasEverBeenReady) {
setHasEverBeenReady(true);
}
}, [feeds, feedUrls, hasEverBeenReady]);
return hasEverBeenReady;
};