UNPKG

@replyke/core

Version:

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

195 lines 8.58 kB
import { useCallback, useMemo, useRef } from "react"; import { useReplykeDispatch, useReplykeSelector } from "../../store/hooks"; import { initializeList, updateFilters, setSpaceListLoading, setSpaceListSpaces, incrementPage, selectSpaceList, selectSpaceListSpaces, selectSpaceListLoading, selectSpaceListHasMore, selectSpaceListFilters, selectSpaceListConfig, } from "../../store/slices/spaceListsSlice"; import useSpaceListActions from "./useSpaceListActions"; import { handleError } from "../../utils/handleError"; function useSpaceList({ listId }) { const dispatch = useReplykeDispatch(); // Get state from Redux (parameterized selectors) const spaceList = useReplykeSelector((state) => selectSpaceList(state, listId)); const spaces = useReplykeSelector((state) => selectSpaceListSpaces(state, listId)); const loading = useReplykeSelector((state) => selectSpaceListLoading(state, listId)); const hasMore = useReplykeSelector((state) => selectSpaceListHasMore(state, listId)); const filters = useReplykeSelector((state) => selectSpaceListFilters(state, listId)); const config = useReplykeSelector((state) => selectSpaceListConfig(state, listId)); // Get space actions hook const spaceActions = useSpaceListActions(); // Debounce timer for filter changes const debounceTimer = useRef(null); // Fetch spaces function (always triggers a fetch) const handleFetchSpaces = useCallback((newFilters, newConfig, options) => { // Apply config defaults if not provided const configWithDefaults = { limit: 20, ...newConfig, }; // Ensure Redux state is initialized and update filters/config dispatch(initializeList({ listId })); dispatch(updateFilters({ listId, filters: newFilters, config: configWithDefaults, options, })); // Clear spaces immediately if requested if (options?.clearImmediately) { dispatch(setSpaceListSpaces({ listId, spaces: [], append: false })); } // Define the fetch logic const performFetch = async () => { // Use the applied config (configWithDefaults is the source of truth for this fetch) const currentConfig = { limit: configWithDefaults.limit }; // Build final filters by taking current state and applying new filters const currentState = spaceList || { sortBy: "newest", searchSlug: null, searchName: null, searchDescription: null, searchAny: null, readingPermission: null, memberOf: false, parentSpaceId: null, }; const finalFilters = { ...currentState }; // Apply resetUnspecified logic (only reset filter properties) if (options?.resetUnspecified) { finalFilters.sortBy = "newest"; finalFilters.searchSlug = null; finalFilters.searchName = null; finalFilters.searchDescription = null; finalFilters.searchAny = null; finalFilters.readingPermission = null; finalFilters.memberOf = false; finalFilters.parentSpaceId = null; } // Apply new filters Object.keys(newFilters).forEach((key) => { if (newFilters[key] !== undefined) { finalFilters[key] = newFilters[key]; } }); if (!finalFilters.sortBy) return; // sortBy is required dispatch(setSpaceListLoading({ listId, loading: true })); try { await spaceActions.fetchSpaces(listId, { page: 1, // User-controlled filters from Redux state + new filters sortBy: finalFilters.sortBy, searchSlug: finalFilters.searchSlug, searchName: finalFilters.searchName, searchDescription: finalFilters.searchDescription, searchAny: finalFilters.searchAny, readingPermission: finalFilters.readingPermission, memberOf: finalFilters.memberOf, parentSpaceId: finalFilters.parentSpaceId, // Configuration parameters from current config limit: currentConfig.limit, }); } catch (err) { console.error(`[SpaceList] Failed to fetch spaces for listId: ${listId}`, err); } }; // Execute immediately if requested, otherwise debounce // For initial loads (empty filters object), make it immediate by default const shouldBeImmediate = options?.fetchImmediately || 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(() => { performFetch(); }, 800); // 800ms debounce delay } }, [dispatch, listId, spaceList, config, spaceActions.fetchSpaces]); // Load more function const loadMore = useCallback(async () => { if (!spaceList || loading || !hasMore) return; // Check if fetchSpaces has been called before (safeguard) if (!config) { console.error(`[SpaceList] loadMore called before fetchSpaces for listId: ${listId}. ` + `fetchSpaces must be called first to initialize configuration.`); return; } const nextPage = spaceList.page + 1; dispatch(incrementPage(listId)); // Directly fetch the next page try { await spaceActions.fetchSpaces(listId, { page: nextPage, // User-controlled filters from Redux state sortBy: spaceList.sortBy, searchSlug: spaceList.searchSlug, searchName: spaceList.searchName, searchDescription: spaceList.searchDescription, searchAny: spaceList.searchAny, readingPermission: spaceList.readingPermission, memberOf: spaceList.memberOf, parentSpaceId: spaceList.parentSpaceId, // Configuration parameters from state (single source of truth) limit: config.limit, }); } catch (err) { console.error(`[SpaceList] Failed to load more spaces for listId: ${listId}`, err); } }, [dispatch, listId, config, spaceList, loading, hasMore, spaceActions.fetchSpaces]); // Create space function const createSpace = useCallback(async ({ insertPosition, ...restOfProps }) => { try { const newSpace = await spaceActions.createSpace(listId, { ...restOfProps, insertPosition, }); return newSpace; } catch (err) { handleError(err, "Failed to create space"); } }, [spaceActions.createSpace, dispatch, listId]); // Delete space function const deleteSpace = useCallback(async ({ spaceId }) => { try { await spaceActions.deleteSpace(listId, { spaceId }); } catch (err) { handleError(err, "Failed to delete space"); } }, [spaceActions.deleteSpace, dispatch, listId]); return useMemo(() => ({ spaces, loading, hasMore, sortBy: filters?.sortBy || null, searchSlug: filters?.searchSlug || null, searchName: filters?.searchName || null, searchDescription: filters?.searchDescription || null, searchAny: filters?.searchAny || null, readingPermission: filters?.readingPermission || null, memberOf: filters?.memberOf || false, parentSpaceId: filters?.parentSpaceId || null, fetchSpaces: handleFetchSpaces, loadMore, createSpace, deleteSpace, }), [ spaces, loading, hasMore, filters, handleFetchSpaces, loadMore, createSpace, deleteSpace, ]); } export default useSpaceList; //# sourceMappingURL=useSpaceList.js.map