@applicaster/zapp-react-native-ui-components 13.0.8-rc.2 → 13.0.9-alpha.1049520314

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/Components/Cell/Cell.tsx +91 -64
  2. package/Components/Cell/CellWithFocusable.tsx +3 -0
  3. package/Components/Cell/__tests__/CellWIthFocusable.test.js +3 -2
  4. package/Components/FeedLoader/FeedLoader.tsx +4 -14
  5. package/Components/FeedLoader/FeedLoaderHOC.tsx +19 -0
  6. package/Components/FeedLoader/index.js +2 -8
  7. package/Components/Focusable/Focusable.tsx +8 -0
  8. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +5 -6
  9. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  10. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +0 -12
  11. package/Components/MasterCell/DefaultComponents/Text/index.tsx +26 -6
  12. package/Components/MasterCell/utils/behaviorProvider.ts +82 -14
  13. package/Components/MasterCell/utils/index.ts +11 -5
  14. package/Components/River/RefreshControl.tsx +11 -17
  15. package/Components/River/__tests__/river.test.js +12 -26
  16. package/Components/TextInputTv/__tests__/__snapshots__/TextInputTv.test.js.snap +13 -0
  17. package/Components/TextInputTv/index.tsx +11 -0
  18. package/Contexts/CellFocusedStateContext/index.tsx +27 -0
  19. package/Contexts/ScreenContext/index.tsx +46 -6
  20. package/Decorators/RiverFeedLoader/__tests__/__snapshots__/riverFeedLoader.test.tsx.snap +221 -209
  21. package/Decorators/RiverFeedLoader/__tests__/riverFeedLoader.test.tsx +14 -16
  22. package/Decorators/RiverFeedLoader/__tests__/utils.test.ts +0 -20
  23. package/Decorators/RiverFeedLoader/index.tsx +22 -4
  24. package/Decorators/RiverFeedLoader/utils/index.ts +0 -18
  25. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +368 -0
  26. package/Decorators/ZappPipesDataConnector/index.tsx +20 -5
  27. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +266 -0
  28. package/Decorators/ZappPipesDataConnector/utils/mongoFilter.ts +738 -0
  29. package/Decorators/ZappPipesDataConnector/utils/useFilter.tsx +159 -0
  30. package/package.json +5 -5
  31. package/Components/River/__tests__/__snapshots__/river.test.js.snap +0 -27
@@ -0,0 +1,368 @@
1
+ import React from "react";
2
+ import * as useFeedLoaderModule from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader";
3
+ import * as useFeedRefreshModule from "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedRefresh";
4
+ import { favoritesListener } from "@applicaster/zapp-react-native-bridge/Favorites";
5
+ import { UrlFeedResolver } from "../resolvers/UrlFeedResolver";
6
+ import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUtils";
7
+ import * as ZappPipesModule from "@applicaster/zapp-pipes-v2-client";
8
+
9
+ const mockSubscribeForKeyChanges = jest.fn();
10
+
11
+ jest
12
+ .spyOn(ZappPipesModule, "subscribeForKeyChanges")
13
+ .mockImplementation(mockSubscribeForKeyChanges);
14
+
15
+ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedLoader");
16
+
17
+ jest.mock(
18
+ "@applicaster/zapp-react-native-utils/reactHooks/feed/useFeedRefresh"
19
+ );
20
+
21
+ jest.mock("@applicaster/zapp-pipes-v2-client");
22
+
23
+ jest.mock("@applicaster/zapp-react-native-bridge/Favorites", () => ({
24
+ favoritesListener: {
25
+ on: jest.fn(),
26
+ removeHandler: jest.fn(),
27
+ },
28
+ }));
29
+
30
+ jest.mock("../utils", () => ({
31
+ isVerticalListOrGrid: jest.fn(
32
+ (component) =>
33
+ component?.type === "vertical_list" || component?.type === "vertical_grid"
34
+ ),
35
+ }));
36
+
37
+ const componentRequiredKeys = { rules: { item_limit: 10 } };
38
+
39
+ describe("UrlFeedResolver", () => {
40
+ const mockChildren = jest.fn(() => <div data-testid="mock-children" />);
41
+ const mockReloadData = jest.fn();
42
+ const mockLoadNext = jest.fn();
43
+
44
+ const mockAddDataSourceListener = jest.fn().mockReturnValue(jest.fn());
45
+
46
+ beforeEach(() => {
47
+ jest.clearAllMocks();
48
+
49
+ (useFeedLoaderModule.useFeedLoader as jest.Mock).mockReturnValue({
50
+ reloadData: mockReloadData,
51
+ loadNext: mockLoadNext,
52
+ error: null,
53
+ loading: false,
54
+ url: "test-url",
55
+ data: { entry: [{ id: "1" }] },
56
+ });
57
+ });
58
+
59
+ it("should use feedUrl directly when provided", () => {
60
+ const props = {
61
+ feedUrl: "https://example.com/feed",
62
+ component: { ...componentRequiredKeys } as any,
63
+ children: mockChildren,
64
+ riverId: "test-river",
65
+ };
66
+
67
+ renderWithProviders(<UrlFeedResolver {...props} />);
68
+
69
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
70
+ feedUrl: "https://example.com/feed",
71
+ pipesOptions: expect.any(Object),
72
+ });
73
+ });
74
+
75
+ it("should use component.data.source when no feedUrl is provided", () => {
76
+ const props = {
77
+ component: {
78
+ data: {
79
+ source: "https://example.com/source",
80
+ },
81
+ ...componentRequiredKeys,
82
+ } as any,
83
+ children: mockChildren,
84
+ riverId: "test-river",
85
+ };
86
+
87
+ renderWithProviders(<UrlFeedResolver {...props} />);
88
+
89
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
90
+ feedUrl: "https://example.com/source",
91
+ pipesOptions: expect.any(Object),
92
+ });
93
+ });
94
+
95
+ it("should handle FAVOURITES type correctly", () => {
96
+ const props = {
97
+ component: {
98
+ data: {
99
+ source: "favorites-source",
100
+ type: "FAVOURITES",
101
+ },
102
+ ...componentRequiredKeys,
103
+ } as any,
104
+ children: mockChildren,
105
+ riverId: "test-river",
106
+ };
107
+
108
+ renderWithProviders(<UrlFeedResolver {...props} />);
109
+
110
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
111
+ feedUrl: "favorites-source",
112
+ pipesOptions: expect.objectContaining({
113
+ clearCache: true,
114
+ loadLocalFavorites: true,
115
+ }),
116
+ });
117
+
118
+ expect(favoritesListener.on).toHaveBeenCalledWith(
119
+ "FAVORITES_CHANGED",
120
+ mockReloadData
121
+ );
122
+ });
123
+
124
+ it("should handle APPLICASTER_COLLECTION type correctly", () => {
125
+ const props = {
126
+ component: {
127
+ data: {
128
+ source: "collection-id",
129
+ type: "APPLICASTER_COLLECTION",
130
+ },
131
+ ...componentRequiredKeys,
132
+ } as any,
133
+ children: mockChildren,
134
+ riverId: "test-river",
135
+ };
136
+
137
+ renderWithProviders(<UrlFeedResolver {...props} />);
138
+
139
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
140
+ feedUrl:
141
+ "applicaster://fetchData?type=APPLICASTER_COLLECTION&collectionId=collection-id",
142
+ pipesOptions: expect.any(Object),
143
+ });
144
+ });
145
+
146
+ it("should handle APPLICASTER_CATEGORY type correctly", () => {
147
+ const props = {
148
+ component: {
149
+ data: {
150
+ source: "category-id",
151
+ type: "APPLICASTER_CATEGORY",
152
+ },
153
+ ...componentRequiredKeys,
154
+ } as any,
155
+ children: mockChildren,
156
+ riverId: "test-river",
157
+ plugins: [],
158
+ };
159
+
160
+ renderWithProviders(<UrlFeedResolver {...props} />);
161
+
162
+ expect(useFeedLoaderModule.useFeedLoader).toHaveBeenCalledWith({
163
+ feedUrl: expect.stringContaining(
164
+ "applicaster://fetchData?type=APPLICASTER_CATEGORY&categoryId=category-id"
165
+ ),
166
+ pipesOptions: expect.any(Object),
167
+ });
168
+ });
169
+
170
+ it("should set up URL context key changes listener", () => {
171
+ const props = {
172
+ feedUrl: "https://example.com/feed",
173
+ component: { ...componentRequiredKeys } as any,
174
+ children: mockChildren,
175
+ riverId: "test-river",
176
+ };
177
+
178
+ renderWithProviders(<UrlFeedResolver {...props} />);
179
+
180
+ expect(mockSubscribeForKeyChanges.mock.calls[0][0]).toMatchObject({
181
+ url: "https://example.com/feed",
182
+ callback: mockReloadData,
183
+ });
184
+ });
185
+
186
+ it("should set up pipesv2 URL listener from plugin if available", () => {
187
+ const props = {
188
+ feedUrl: "pipesv2://test-plugin/endpoint",
189
+ component: { ...componentRequiredKeys } as any,
190
+ children: mockChildren,
191
+ riverId: "test-river",
192
+ plugins: [
193
+ {
194
+ identifier: "test-plugin",
195
+ module: {
196
+ addDataSourceListener: mockAddDataSourceListener,
197
+ },
198
+ },
199
+ ] as any,
200
+ };
201
+
202
+ renderWithProviders(<UrlFeedResolver {...props} />);
203
+
204
+ expect(mockAddDataSourceListener).toHaveBeenCalledWith(mockReloadData);
205
+ expect(mockSubscribeForKeyChanges).not.toHaveBeenCalled();
206
+ });
207
+
208
+ it("should apply item limit from component rules", () => {
209
+ const props = {
210
+ feedUrl: "https://example.com/feed",
211
+ component: {
212
+ rules: {
213
+ item_limit: "2",
214
+ },
215
+ } as any,
216
+ children: mockChildren,
217
+ riverId: "test-river",
218
+ };
219
+
220
+ (useFeedLoaderModule.useFeedLoader as jest.Mock).mockReturnValue({
221
+ reloadData: mockReloadData,
222
+ loadNext: mockLoadNext,
223
+ error: null,
224
+ loading: false,
225
+ url: "test-url",
226
+ data: { entry: [{ id: "1" }, { id: "2" }, { id: "3" }] },
227
+ });
228
+
229
+ renderWithProviders(<UrlFeedResolver {...props} />);
230
+
231
+ expect(mockChildren).toHaveBeenCalledWith(
232
+ expect.objectContaining({
233
+ zappPipesData: expect.objectContaining({
234
+ data: expect.objectContaining({
235
+ entry: [{ id: "1" }, { id: "2" }],
236
+ }),
237
+ }),
238
+ })
239
+ );
240
+ });
241
+
242
+ it("should conditionally include loadNextData based on isLast and component type", () => {
243
+ // Case 1: isLast=true, should not include loadNextData regardless of component type
244
+ const props1 = {
245
+ feedUrl: "https://example.com/feed",
246
+ component: {
247
+ type: "vertical_list",
248
+ ...componentRequiredKeys,
249
+ } as any,
250
+ children: mockChildren,
251
+ riverId: "test-river",
252
+ isLast: true,
253
+ };
254
+
255
+ renderWithProviders(<UrlFeedResolver {...props1} />);
256
+
257
+ expect(mockChildren).toHaveBeenCalledWith(
258
+ expect.objectContaining({
259
+ loadNextData: mockLoadNext,
260
+ })
261
+ );
262
+
263
+ mockChildren.mockClear();
264
+
265
+ // Case 2: isLast=false, vertical_list component, should not include loadNextData
266
+ const props2 = {
267
+ feedUrl: "https://example.com/feed",
268
+ component: {
269
+ type: "vertical_list",
270
+ ...componentRequiredKeys,
271
+ } as any,
272
+ children: mockChildren,
273
+ riverId: "test-river",
274
+ isLast: false,
275
+ };
276
+
277
+ renderWithProviders(<UrlFeedResolver {...props2} />);
278
+
279
+ expect(mockChildren).toHaveBeenCalledWith(
280
+ expect.objectContaining({
281
+ loadNextData: undefined,
282
+ })
283
+ );
284
+
285
+ mockChildren.mockClear();
286
+
287
+ // Case 3: isLast=false, non-vertical component, should include loadNextData
288
+ const props3 = {
289
+ feedUrl: "https://example.com/feed",
290
+ component: {
291
+ type: "horizontal_list",
292
+ ...componentRequiredKeys,
293
+ } as any,
294
+ children: mockChildren,
295
+ riverId: "test-river",
296
+ isLast: false,
297
+ };
298
+
299
+ renderWithProviders(<UrlFeedResolver {...props3} />);
300
+
301
+ expect(mockChildren).toHaveBeenCalledWith(
302
+ expect.objectContaining({
303
+ loadNextData: mockLoadNext,
304
+ })
305
+ );
306
+ });
307
+
308
+ it("should pass correct zappPipesDataProps to children", () => {
309
+ const props = {
310
+ feedUrl: "https://example.com/feed",
311
+ component: { ...componentRequiredKeys } as any,
312
+ children: mockChildren,
313
+ riverId: "test-river",
314
+ };
315
+
316
+ renderWithProviders(<UrlFeedResolver {...props} />);
317
+
318
+ expect(mockChildren).toHaveBeenCalledWith({
319
+ zappPipesData: {
320
+ url: "test-url",
321
+ loading: false,
322
+ data: { entry: [{ id: "1" }] },
323
+ error: null,
324
+ },
325
+ reloadData: mockReloadData,
326
+ loadNextData: mockLoadNext,
327
+ });
328
+ });
329
+
330
+ it("should apply feed refresh hook", () => {
331
+ const props = {
332
+ feedUrl: "https://example.com/feed",
333
+ component: { ...componentRequiredKeys } as any,
334
+ children: mockChildren,
335
+ riverId: "test-river",
336
+ };
337
+
338
+ renderWithProviders(<UrlFeedResolver {...props} />);
339
+
340
+ expect(useFeedRefreshModule.useFeedRefresh).toHaveBeenCalledWith({
341
+ reloadData: mockReloadData,
342
+ component: props.component,
343
+ });
344
+ });
345
+
346
+ it("should clean up listeners on unmount", () => {
347
+ const props = {
348
+ component: {
349
+ data: {
350
+ source: "favorites-source",
351
+ type: "FAVOURITES",
352
+ },
353
+ ...componentRequiredKeys,
354
+ } as any,
355
+ children: mockChildren,
356
+ riverId: "test-river",
357
+ };
358
+
359
+ const { unmount } = renderWithProviders(<UrlFeedResolver {...props} />);
360
+
361
+ unmount();
362
+
363
+ expect(favoritesListener.removeHandler).toHaveBeenCalledWith(
364
+ "FAVORITES_CHANGED",
365
+ mockReloadData
366
+ );
367
+ });
368
+ });
@@ -17,9 +17,11 @@ import {
17
17
 
18
18
  import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
19
19
  import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
20
+ import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
21
+ import { subscribeForKeyChanges } from "@applicaster/zapp-pipes-v2-client";
20
22
 
21
23
  import { isVerticalListOrGrid } from "./utils";
22
- import { subscribeForUrlContextKeyChanges } from "@applicaster/zapp-pipes-v2-client";
24
+ import { useFilter } from "./utils/useFilter";
23
25
 
24
26
  type Props = {
25
27
  component: ZappUIComponent;
@@ -204,7 +206,7 @@ export function zappPipesDataConnector(
204
206
  Component: React.FC<any> | React.ComponentClass<any>
205
207
  ) {
206
208
  return function WrappedWithZappPipesData(props: Props) {
207
- const { screenData } = useRoute();
209
+ const { pathname, screenData } = useRoute();
208
210
  const { plugins } = usePickFromState(["plugins"]);
209
211
 
210
212
  const screenContextData = useScreenContext();
@@ -286,6 +288,8 @@ export function zappPipesDataConnector(
286
288
  componentIndex
287
289
  );
288
290
 
291
+ const screenStateStore = useScreenStateStore();
292
+
289
293
  useEffect(() => {
290
294
  if (dataSourceUrl?.includes("pipesv2://") && reloadData) {
291
295
  const addListener = getListenerFromPlugin(dataSourceUrl, plugins);
@@ -294,9 +298,14 @@ export function zappPipesDataConnector(
294
298
  return addListener(reloadData);
295
299
  }
296
300
  } else {
297
- return subscribeForUrlContextKeyChanges(dataSourceUrl, {}, reloadData);
301
+ return subscribeForKeyChanges({
302
+ url: dataSourceUrl,
303
+ pathname,
304
+ screenStateStore,
305
+ callback: reloadData,
306
+ });
298
307
  }
299
- }, [dataSourceUrl, reloadData]);
308
+ }, [dataSourceUrl, reloadData, pathname, screenStateStore]);
300
309
 
301
310
  useEffect(() => {
302
311
  if (type === FAVORITES_TYPE && reloadData) {
@@ -316,12 +325,17 @@ export function zappPipesDataConnector(
316
325
  [staticFeed?.loading, staticFeed?.data, data?.next, loading, data]
317
326
  );
318
327
 
328
+ const applyItemFilter = useFilter({ url, loading, data, error }, component);
329
+
319
330
  const zappPipesDataProps = useMemo(() => {
320
331
  const loadNextData =
321
332
  !isLast && isVerticalListOrGrid(component) ? undefined : loadNext;
322
333
 
323
334
  return {
324
- zappPipesData: applyItemLimit(getZappPipesData(), component),
335
+ zappPipesData: applyItemLimit(
336
+ applyItemFilter(getZappPipesData()),
337
+ component
338
+ ),
325
339
  reloadData,
326
340
  loadNextData,
327
341
  };
@@ -335,6 +349,7 @@ export function zappPipesDataConnector(
335
349
  data,
336
350
  isLast,
337
351
  component,
352
+ applyItemFilter,
338
353
  ]);
339
354
 
340
355
  useFeedRefresh({
@@ -0,0 +1,266 @@
1
+ /// <reference types="@applicaster/applicaster-types" />
2
+ /// <reference types="@applicaster/zapp-react-native-ui-components" />
3
+ import React, { useEffect, useMemo } from "react";
4
+ import * as R from "ramda";
5
+ import { Platform } from "react-native";
6
+ import Url from "url";
7
+ import { favoritesListener } from "@applicaster/zapp-react-native-bridge/Favorites";
8
+ import {
9
+ getInflatedDataSourceUrl,
10
+ getSearchContext,
11
+ useFeedLoader,
12
+ useFeedRefresh,
13
+ useRoute,
14
+ } from "@applicaster/zapp-react-native-utils/reactHooks";
15
+
16
+ import { ComponentDataSourceContext, ZappPipesDataProps } from "../types";
17
+ import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
18
+ import { isVerticalListOrGrid } from "../utils";
19
+ import { useFilter } from "../utils/useFilter";
20
+ import { subscribeForKeyChanges } from "@applicaster/zapp-pipes-v2-client";
21
+
22
+ const FAVORITES_TYPE = "FAVOURITES";
23
+
24
+ function getDataSourceUrl({
25
+ component,
26
+ feedUrl,
27
+ screenData,
28
+ entryContext,
29
+ searchContext,
30
+ screenContext,
31
+ }): { dataSourceUrl: string | null; type: string | null } {
32
+ if (feedUrl) {
33
+ return { dataSourceUrl: feedUrl, type: null };
34
+ }
35
+
36
+ if (component.data) {
37
+ const {
38
+ data: { source, type, mapping },
39
+ } = component;
40
+
41
+ if (type === FAVORITES_TYPE) {
42
+ return { dataSourceUrl: source || FAVORITES_TYPE, type };
43
+ }
44
+
45
+ if (source) {
46
+ if (type === "APPLICASTER_COLLECTION" && !R.includes("://", source)) {
47
+ return {
48
+ dataSourceUrl: `applicaster://fetchData?type=${type}&collectionId=${source}`,
49
+ type,
50
+ };
51
+ }
52
+
53
+ if (type === "APPLICASTER_CATEGORY" && !R.includes("://", source)) {
54
+ return {
55
+ dataSourceUrl: `applicaster://fetchData?type=${type}&categoryId=${source}&platform=${Platform.OS}`,
56
+ type,
57
+ };
58
+ }
59
+
60
+ if (mapping) {
61
+ const contexts = {
62
+ entry: entryContext,
63
+ screen: screenContext || screenData,
64
+ search: getSearchContext(searchContext, mapping),
65
+ };
66
+
67
+ const dataSourceUrl = getInflatedDataSourceUrl({
68
+ source,
69
+ mapping,
70
+ contexts,
71
+ });
72
+
73
+ if (!dataSourceUrl) {
74
+ return { dataSourceUrl: null, type: null };
75
+ }
76
+
77
+ return { dataSourceUrl, type };
78
+ }
79
+
80
+ return { dataSourceUrl: source, type };
81
+ }
82
+ }
83
+
84
+ return { dataSourceUrl: null, type: null };
85
+ }
86
+
87
+ function getMethodAndParams(component) {
88
+ const method: "get" | "post" = R.pathOr("get", ["data", "method"], component);
89
+
90
+ const bodyParams: Record<string, unknown> = R.pathOr(
91
+ {},
92
+ ["data", "bodyParams"],
93
+ component
94
+ );
95
+
96
+ return {
97
+ method,
98
+ bodyParams,
99
+ };
100
+ }
101
+
102
+ function getListenerFromPlugin(
103
+ dataSourceUrl: string,
104
+ plugins: QuickBrickPlugin[]
105
+ ): AddDataSourceListener | void {
106
+ const url = Url.parse(dataSourceUrl, false);
107
+
108
+ const addListener = R.compose(
109
+ R.path(["module", "addDataSourceListener"]),
110
+ R.find(R.propEq("identifier", url.host))
111
+ )(plugins);
112
+
113
+ return addListener;
114
+ }
115
+
116
+ function applyItemLimit(zappPipesData, component) {
117
+ if (!zappPipesData || !zappPipesData.data) {
118
+ return zappPipesData;
119
+ }
120
+
121
+ const { data } = zappPipesData;
122
+
123
+ const {
124
+ rules: { item_limit },
125
+ } = component;
126
+
127
+ if (item_limit && data.entry && data.entry.length) {
128
+ return {
129
+ ...zappPipesData,
130
+ data: {
131
+ ...data,
132
+ entry: R.slice(
133
+ 0,
134
+ Math.min(Number(item_limit), R.length(data.entry)),
135
+ data.entry
136
+ ),
137
+ },
138
+ };
139
+ }
140
+
141
+ return zappPipesData;
142
+ }
143
+
144
+ type UrlFeedResolverProps = ComponentDataSourceContext & {
145
+ children: (dataProps: ZappPipesDataProps) => React.ReactNode;
146
+ };
147
+
148
+ export function UrlFeedResolver({
149
+ children,
150
+ component,
151
+ feedUrl,
152
+ isLast,
153
+ entryContext,
154
+ screenContext,
155
+ searchContext,
156
+ screenData,
157
+ plugins,
158
+ }: UrlFeedResolverProps) {
159
+ const { dataSourceUrl, type } = useMemo(
160
+ () =>
161
+ getDataSourceUrl({
162
+ component,
163
+ feedUrl,
164
+ screenData,
165
+ entryContext,
166
+ searchContext,
167
+ screenContext,
168
+ }),
169
+ [
170
+ component?.id,
171
+ feedUrl,
172
+ entryContext,
173
+ searchContext,
174
+ screenContext,
175
+ screenData?.id,
176
+ ]
177
+ );
178
+
179
+ const shouldClearCache = useMemo(
180
+ () => Boolean(component?.rules?.clear_cache_on_reload),
181
+ [component?.id]
182
+ );
183
+
184
+ const pipesOptions = useMemo(
185
+ () => ({
186
+ clearCache: type === FAVORITES_TYPE,
187
+ loadLocalFavorites: type === FAVORITES_TYPE,
188
+ silentRefresh: !shouldClearCache,
189
+ ...getMethodAndParams(component),
190
+ }),
191
+ [type, shouldClearCache, component]
192
+ );
193
+
194
+ const { reloadData, loadNext, error, loading, url, data } = useFeedLoader({
195
+ feedUrl: dataSourceUrl ?? "",
196
+ pipesOptions,
197
+ });
198
+
199
+ const { pathname } = useRoute();
200
+ const screenStateStore = useScreenStateStore();
201
+
202
+ // Setup listeners for data source URL
203
+ useEffect(() => {
204
+ if (!reloadData) {
205
+ return;
206
+ }
207
+
208
+ if (dataSourceUrl?.includes("pipesv2://")) {
209
+ (
210
+ getListenerFromPlugin(dataSourceUrl, plugins) as AddDataSourceListener
211
+ )?.(reloadData);
212
+ } else {
213
+ return subscribeForKeyChanges({
214
+ url: dataSourceUrl,
215
+ pathname,
216
+ screenStateStore,
217
+ callback: reloadData,
218
+ });
219
+ }
220
+ }, [dataSourceUrl, reloadData, pathname, screenStateStore]);
221
+
222
+ // Setup favorites listener
223
+ useEffect(() => {
224
+ if (type === FAVORITES_TYPE && reloadData) {
225
+ favoritesListener.on("FAVORITES_CHANGED", reloadData);
226
+
227
+ return () => {
228
+ favoritesListener.removeHandler("FAVORITES_CHANGED", reloadData);
229
+ };
230
+ }
231
+ }, [type, reloadData]);
232
+
233
+ // Apply feed refresh hook
234
+ useFeedRefresh({
235
+ reloadData,
236
+ component,
237
+ });
238
+
239
+ const loadNextData = useMemo(
240
+ () => (!isLast && isVerticalListOrGrid(component) ? undefined : loadNext),
241
+ [isLast, component, loadNext]
242
+ );
243
+
244
+ const applyItemFilter = useFilter({ url, loading, data, error }, component);
245
+
246
+ const zappPipesDataProps = useMemo(() => {
247
+ const pipeData = { url, loading, data, error };
248
+
249
+ return {
250
+ zappPipesData: applyItemLimit(applyItemFilter(pipeData), component),
251
+ reloadData,
252
+ loadNextData,
253
+ };
254
+ }, [
255
+ url,
256
+ loading,
257
+ data,
258
+ error,
259
+ component,
260
+ reloadData,
261
+ loadNextData,
262
+ applyItemFilter,
263
+ ]);
264
+
265
+ return <>{children(zappPipesDataProps)}</>;
266
+ }