177 lines
4.7 KiB
JavaScript

import { useState, useCallback } from 'react'
import axios from 'axios'
export const useTableData = ({
url,
pageSize,
initialPage = 1,
onDataChange,
filters = {},
sorter = {}
}) => {
const [pages, setPages] = useState([])
const [hasMore, setHasMore] = useState(true)
const [hasPrevious, setHasPrevious] = useState(initialPage > 1)
const [loading, setLoading] = useState(true)
const [lazyLoading, setLazyLoading] = useState(false)
const [totalPages, setTotalPages] = useState(0)
const [loadedPages, setLoadedPages] = useState([])
const [loadingPages, setLoadingPages] = useState(new Set())
const [currentLoadedPageNumber, setCurrentLoadedPageNumber] =
useState(initialPage)
const createSkeletonData = useCallback(() => {
return Array(pageSize)
.fill(null)
.map(() => ({
_id: `skeleton-${Math.random().toString(36).substring(2, 15)}`,
isSkeleton: true
}))
}, [pageSize])
const fetchData = useCallback(
async (pageNum = 1, append = false, prepend = false) => {
if (loadingPages.has(pageNum)) {
return
}
try {
setLoadingPages((prev) => new Set([...prev, pageNum]))
const response = await axios.get(url, {
params: {
page: pageNum,
limit: pageSize,
...filters,
sort: sorter.field,
order: sorter.order
},
headers: {
Accept: 'application/json'
},
withCredentials: true
})
const newData = response.data
const totalCount = parseInt(
response.headers['x-total-count'] || '0',
10
)
setTotalPages(Math.ceil(totalCount / pageSize))
setHasMore(newData.length >= pageSize)
setHasPrevious(pageNum > 1)
if (append) {
setPages((prev) => {
const filteredPages = prev.map((page) => ({
...page,
items: page.items.filter((item) => !item.isSkeleton)
}))
const relevantPages = filteredPages.slice(-2)
return [...relevantPages, { pageNum, items: newData }]
})
setLoadedPages((prev) => {
const relevantPages = prev.slice(-2)
return [...relevantPages, pageNum]
})
} else if (prepend) {
setPages((prev) => {
const filteredPages = prev.map((page) => ({
...page,
items: page.items.filter((item) => !item.isSkeleton)
}))
const relevantPages = filteredPages.slice(0, 2)
return [{ pageNum, items: newData }, ...relevantPages]
})
setLoadedPages((prev) => {
const relevantPages = prev.slice(0, 2)
return [pageNum, ...relevantPages]
})
} else {
setPages([{ pageNum, items: newData }])
setLoadedPages([pageNum])
}
if (onDataChange) {
onDataChange(newData)
}
setLoading(false)
setLazyLoading(false)
} catch (error) {
setPages((prev) =>
prev.map((page) => ({
...page,
items: page.items.filter((item) => !item.isSkeleton)
}))
)
setLoading(false)
setLazyLoading(false)
throw error
} finally {
setLoadingPages((prev) => {
const newSet = new Set(prev)
newSet.delete(pageNum)
return newSet
})
}
},
[url, pageSize, filters, sorter, onDataChange, loadingPages]
)
const reload = useCallback(() => {
setCurrentLoadedPageNumber(1)
setLoadedPages([1])
return fetchData(1)
}, [fetchData])
const updateData = useCallback((_id, updatedData) => {
setPages((prevPages) =>
prevPages.map((page) => ({
...page,
items: page.items.map((item) =>
item._id === _id ? { ...item, ...updatedData } : item
)
}))
)
}, [])
const goToPage = useCallback(
(pageNum) => {
if (pageNum > 0 && pageNum <= totalPages) {
setCurrentLoadedPageNumber(pageNum)
const pagesToLoad = [pageNum - 1, pageNum, pageNum + 1].filter(
(p) => p > 0 && p <= totalPages
)
setLoadedPages(pagesToLoad)
return Promise.all(
pagesToLoad.map((p) => fetchData(p, p > pageNum, p < pageNum))
)
}
},
[fetchData, totalPages]
)
return {
pages,
hasMore,
hasPrevious,
loading,
lazyLoading,
totalPages,
loadedPages,
loadingPages,
currentLoadedPageNumber,
createSkeletonData,
fetchData,
reload,
updateData,
goToPage,
setCurrentLoadedPageNumber,
setLazyLoading,
setPages,
setLoadedPages
}
}