UNPKG

@replyke/core

Version:

Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.

370 lines 19.3 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { useCallback, useMemo, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { initializeList, updateFiltersAndSort, setEntityListLoading, setEntityListEntities, incrementPage, selectEntityList, selectEntityListEntities, selectEntityListLoading, selectEntityListHasMore, selectEntityListFilters, selectEntityListConfig, } from "../../store/slices/entityListsSlice"; import useInfusedData from "./useInfusedData"; import useEntityListActions from "./useEntityListActions"; import { handleError } from "../../utils/handleError"; function useEntityList(_a) { var _this = this; var listId = _a.listId, infuseData = _a.infuseData; var dispatch = useDispatch(); // Get state from Redux var entityList = useSelector(function (state) { return selectEntityList(state, listId); }); var entities = useSelector(function (state) { return selectEntityListEntities(state, listId); }); var loading = useSelector(function (state) { return selectEntityListLoading(state, listId); }); var hasMore = useSelector(function (state) { return selectEntityListHasMore(state, listId); }); var filters = useSelector(function (state) { return selectEntityListFilters(state, listId); }); var config = useSelector(function (state) { return selectEntityListConfig(state, listId); }); // Get entity actions hook var entityActions = useEntityListActions(); // Infused data var infusedEntities = useInfusedData({ entities: entities, infuseData: infuseData }); // Debounce timer for filter changes var debounceTimer = useRef(null); // Fetch entities function (always triggers a fetch) var handleFetchEntities = useCallback(function (newFilters, newConfig, options) { // Apply config defaults if not provided var configWithDefaults = __assign({ sourceId: null, limit: 10 }, newConfig); // Ensure Redux state is initialized and update filters/config dispatch(initializeList({ listId: listId })); dispatch(updateFiltersAndSort({ listId: listId, filters: newFilters, config: configWithDefaults, options: options })); // Clear entities immediately if requested if (options === null || options === void 0 ? void 0 : options.clearImmediately) { dispatch(setEntityListEntities({ listId: listId, entities: [], append: false })); } // Define the fetch logic var performFetch = function () { return __awaiter(_this, void 0, void 0, function () { var currentConfig, currentState, finalFilters, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: currentConfig = { sourceId: configWithDefaults.sourceId, limit: configWithDefaults.limit }; currentState = entityList || { sortBy: "hot", sortDir: null, sortType: "auto", timeFrame: null, userId: null, followedOnly: false, keywordsFilters: null, titleFilters: null, contentFilters: null, attachmentsFilters: null, locationFilters: null, metadataFilters: null, }; finalFilters = __assign({}, currentState); // Apply resetUnspecified logic (only reset filter properties, not config) if (options === null || options === void 0 ? void 0 : options.resetUnspecified) { // Reset only filter properties to defaults, keep config and state properties as-is finalFilters.sortBy = "hot"; finalFilters.sortDir = null; finalFilters.sortType = "auto"; finalFilters.timeFrame = null; finalFilters.userId = null; finalFilters.followedOnly = false; finalFilters.keywordsFilters = null; finalFilters.titleFilters = null; finalFilters.contentFilters = null; finalFilters.attachmentsFilters = null; finalFilters.locationFilters = null; finalFilters.metadataFilters = null; } // Apply new filters Object.keys(newFilters).forEach(function (key) { if (newFilters[key] !== undefined) { finalFilters[key] = newFilters[key]; } }); if (!finalFilters.sortBy) return [2 /*return*/]; // sortBy is required dispatch(setEntityListLoading({ listId: listId, loading: true })); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, entityActions.fetchEntities(listId, { page: 1, // User-controlled filters from Redux state + new filters sortBy: finalFilters.sortBy, sortDir: finalFilters.sortDir, sortType: finalFilters.sortType, timeFrame: finalFilters.timeFrame, userId: finalFilters.userId, followedOnly: finalFilters.followedOnly, locationFilters: finalFilters.locationFilters, keywordsFilters: finalFilters.keywordsFilters, metadataFilters: finalFilters.metadataFilters, titleFilters: finalFilters.titleFilters, contentFilters: finalFilters.contentFilters, attachmentsFilters: finalFilters.attachmentsFilters, // Configuration parameters from current config limit: currentConfig.limit, sourceId: currentConfig.sourceId, })]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: err_1 = _a.sent(); console.error("[EntityListRedux] Failed to fetch entities for listId: ".concat(listId), err_1); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }; // Execute immediately if requested, otherwise debounce // For initial loads (empty filters object), make it immediate by default var shouldBeImmediate = (options === null || options === void 0 ? void 0 : options.immediate) || Object.keys(newFilters).length === 0; if (shouldBeImmediate) { performFetch(); } else { // Clear existing debounce timer if (debounceTimer.current) { clearTimeout(debounceTimer.current); } // Debounce the actual fetch debounceTimer.current = setTimeout(function () { performFetch(); }, 800); // 800ms debounce delay } }, [dispatch, listId, entityList, config, entityActions.fetchEntities]); // Load more function var loadMore = useCallback(function () { return __awaiter(_this, void 0, void 0, function () { var nextPage, err_2; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!entityList || loading || !hasMore) return [2 /*return*/]; // Check if fetchEntities has been called before (safeguard) if (!config) { console.error("[EntityListRedux] loadMore called before fetchEntities for listId: ".concat(listId, ". ") + "fetchEntities must be called first to initialize configuration."); return [2 /*return*/]; } nextPage = entityList.page + 1; dispatch(incrementPage(listId)); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, entityActions.fetchEntities(listId, { page: nextPage, // User-controlled filters from Redux state userId: entityList.userId, followedOnly: entityList.followedOnly, sortBy: entityList.sortBy, sortDir: entityList.sortDir, sortType: entityList.sortType, timeFrame: entityList.timeFrame, locationFilters: entityList.locationFilters, keywordsFilters: entityList.keywordsFilters, metadataFilters: entityList.metadataFilters, titleFilters: entityList.titleFilters, contentFilters: entityList.contentFilters, attachmentsFilters: entityList.attachmentsFilters, // Configuration parameters from state (single source of truth) limit: config.limit, sourceId: config.sourceId, })]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: err_2 = _a.sent(); console.error("[EntityListRedux] Failed to load more entities for listId: ".concat(listId), err_2); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }, [ dispatch, listId, config, entityList, loading, hasMore, entityActions.fetchEntities, ]); // Create entity function var createEntity = useCallback(function (_a) { return __awaiter(_this, void 0, void 0, function () { var newEntity, err_3; var insertPosition = _a.insertPosition, restOfProps = __rest(_a, ["insertPosition"]); return __generator(this, function (_b) { switch (_b.label) { case 0: _b.trys.push([0, 2, , 3]); return [4 /*yield*/, entityActions.createEntity(listId, __assign(__assign({}, restOfProps), { sourceId: (config === null || config === void 0 ? void 0 : config.sourceId) || null, insertPosition: insertPosition }))]; case 1: newEntity = _b.sent(); return [2 /*return*/, newEntity]; case 2: err_3 = _b.sent(); // Error handling is now done in entityActions.createEntity handleError(err_3, "Failed to create entity"); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }, [entityActions.createEntity, dispatch, listId, config]); // Delete entity function var deleteEntity = useCallback(function (_a) { return __awaiter(_this, [_a], void 0, function (_b) { var err_4; var entityId = _b.entityId; return __generator(this, function (_c) { switch (_c.label) { case 0: _c.trys.push([0, 2, , 3]); return [4 /*yield*/, entityActions.deleteEntity(listId, { entityId: entityId })]; case 1: _c.sent(); return [3 /*break*/, 3]; case 2: err_4 = _c.sent(); // Error handling is now done in entityActions.deleteEntity handleError(err_4, "Failed to delete entity"); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }, [entityActions.deleteEntity, dispatch, listId]); // Load more entities when page changes - REMOVED // This useEffect was causing duplicate API calls and race conditions // Load more is now handled directly in the loadMore function // fetchEntities now handles fetching directly when called // No automatic filter change detection needed // Legacy setEntities function for compatibility // const handleSetEntities = useCallback( // (updater: React.SetStateAction<Entity[]>) => { // if (typeof updater === "function") { // const newEntities = updater(entities); // dispatch( // setEntityListEntities({ // listId, // entities: newEntities, // append: false, // }) // ); // } else { // dispatch( // setEntityListEntities({ listId, entities: updater, append: false }) // ); // } // }, // [dispatch, listId, entities] // ); // No automatic initialization - state is only created when fetchEntities is called return useMemo(function () { return ({ entities: entities, // setEntities: handleSetEntities, infusedEntities: infusedEntities, loading: loading, hasMore: hasMore, sortBy: (filters === null || filters === void 0 ? void 0 : filters.sortBy) || null, sortDir: (filters === null || filters === void 0 ? void 0 : filters.sortDir) || null, sortType: (filters === null || filters === void 0 ? void 0 : filters.sortType) || null, timeFrame: (filters === null || filters === void 0 ? void 0 : filters.timeFrame) || null, sourceId: (config === null || config === void 0 ? void 0 : config.sourceId) || null, userId: (filters === null || filters === void 0 ? void 0 : filters.userId) || null, followedOnly: (filters === null || filters === void 0 ? void 0 : filters.followedOnly) || false, keywordsFilters: (filters === null || filters === void 0 ? void 0 : filters.keywordsFilters) || null, titleFilters: (filters === null || filters === void 0 ? void 0 : filters.titleFilters) || null, contentFilters: (filters === null || filters === void 0 ? void 0 : filters.contentFilters) || null, attachmentsFilters: (filters === null || filters === void 0 ? void 0 : filters.attachmentsFilters) || null, locationFilters: (filters === null || filters === void 0 ? void 0 : filters.locationFilters) || null, metadataFilters: (filters === null || filters === void 0 ? void 0 : filters.metadataFilters) || null, fetchEntities: handleFetchEntities, loadMore: loadMore, createEntity: createEntity, deleteEntity: deleteEntity, }); }, [ entities, infusedEntities, loading, hasMore, filters, config, handleFetchEntities, loadMore, createEntity, deleteEntity, ]); } export default useEntityList; //# sourceMappingURL=useEntityList.js.map