Skip to content

Commit 4c51f31

Browse files
committed
✨ [Hooks]: Refine useTableRequest params
1 parent 02f7342 commit 4c51f31

4 files changed

Lines changed: 240 additions & 34 deletions

File tree

app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseTableRequestExample.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ fun UseTableRequestExample() {
4949

5050
// 1. Use useTableRequest for data fetching
5151
val tableRequest = useTableRequest<User>(
52-
requestFn = { page, pageSize -> mockApiRequest(page, pageSize) },
52+
requestFn = { params -> mockApiRequest(params.page, params.pageSize) },
5353
optionsOf = {
5454
initialPageSize = 5
5555
requestOptions = {

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/userequest/UseTableRequest.kt

Lines changed: 156 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@ import xyz.junerver.compose.hooks.useEffect
99
import xyz.junerver.compose.hooks.useLatestRef
1010
import xyz.junerver.compose.hooks.userequest.utils.CachedData
1111
import xyz.junerver.compose.hooks.utils.CacheManager
12+
import xyz.junerver.compose.hooks.usetable.state.SortDescriptor
1213

1314
/**
1415
* Paged request parameters.
1516
*
1617
* @param page Page index (0-based)
1718
* @param pageSize Number of items per page
1819
*/
19-
data class PageParams(
20+
data class TableRequestParams(
2021
val page: Int = 0,
21-
val pageSize: Int = 10
22+
val pageSize: Int = 10,
23+
val sorting: List<SortDescriptor> = emptyList(),
24+
val globalFilter: String = "",
25+
val columnFilters: Map<String, Any?> = emptyMap()
2226
)
2327

2428
/**
@@ -41,6 +45,13 @@ data class TableResult<T>(
4145
* Options for useTableRequest hook.
4246
*/
4347
class UseTableRequestOptions<TData : Any> {
48+
var initialSorting: List<SortDescriptor> = emptyList()
49+
var initialGlobalFilter: String = ""
50+
var initialColumnFilters: Map<String, Any?> = emptyMap()
51+
52+
var triggerOnSortingChange: Boolean = true
53+
var triggerOnFilteringChange: Boolean = true
54+
4455
/**
4556
* Initial page number (default: 0)
4657
*/
@@ -54,7 +65,11 @@ class UseTableRequestOptions<TData : Any> {
5465
/**
5566
* All useRequest options (cacheKey, staleTime, onSuccess, etc.)
5667
*/
57-
var requestOptions: UseRequestOptions<PageParams, TData>.() -> Unit = {}
68+
var requestOptions: UseRequestOptions<TableRequestParams, TData>.() -> Unit = {}
69+
var mergeCacheKey: ((baseKey: String, params: TableRequestParams) -> String)? = null
70+
var setCache: ((data: CachedData<TData>) -> Unit)? = null
71+
var getCache: ((params: TableRequestParams) -> CachedData<TData>?)? = null
72+
var onRequestParams: ((TableRequestParams) -> Unit)? = null
5873
}
5974

6075
/**
@@ -75,12 +90,23 @@ data class TableRequestHolder<T>(
7590
val pageSize: State<Int>,
7691
val total: State<Int>,
7792

93+
// Sorting & filtering states
94+
val sorting: State<List<SortDescriptor>>,
95+
val globalFilter: State<String>,
96+
val columnFilters: State<Map<String, Any?>>,
97+
7898
// Request controls
7999
val refresh: () -> Unit,
80100
val cancel: () -> Unit,
81101

82102
// Pagination controls
83-
val onPageChange: (page: Int, pageSize: Int) -> Unit
103+
val onPageChange: (page: Int, pageSize: Int) -> Unit,
104+
105+
// Sorting & filtering controls
106+
val setSorting: (List<SortDescriptor>) -> Unit,
107+
val setGlobalFilter: (String) -> Unit,
108+
val setColumnFilter: (String, Any?) -> Unit,
109+
val clearFilters: () -> Unit
84110
)
85111

86112
/**
@@ -92,7 +118,7 @@ data class TableRequestHolder<T>(
92118
*
93119
* ## Key Design Principles:
94120
* 1. **Standard Container**: TableResult<T> abstracts away API response differences
95-
* 2. **Fixed Function Signature**: (page, pageSize) -> TableResult<T>
121+
* 2. **Fixed Function Signature**: TableRequestParams -> TableResult<T>
96122
* 3. **User Does Mapping**: Convert API response to TableResult in requestFn
97123
* 4. **Avoid Unnecessary Recompositions**: rows wrapped in State
98124
*
@@ -103,8 +129,8 @@ data class TableRequestHolder<T>(
103129
*
104130
* // Use the hook - map API response to TableResult
105131
* val tableRequest = useTableRequest<User>(
106-
* requestFn = { page, pageSize ->
107-
* val response = api.getUsers(page, pageSize)
132+
* requestFn = { params ->
133+
* val response = api.getUsers(params.page, params.pageSize)
108134
* // Map your API format to TableResult (plain data class)
109135
* TableResult(
110136
* rows = response.items,
@@ -138,12 +164,12 @@ data class TableRequestHolder<T>(
138164
* ```
139165
*
140166
* @param T Row data type
141-
* @param requestFn Async function: (page, pageSize) -> TableResult<T>
167+
* @param requestFn Async function: (params) -> TableResult<T>
142168
* @param optionsOf Configuration DSL
143169
*/
144170
@Composable
145171
fun <T> useTableRequest(
146-
requestFn: suspend (page: Int, pageSize: Int) -> TableResult<T>,
172+
requestFn: suspend (params: TableRequestParams) -> TableResult<T>,
147173
optionsOf: UseTableRequestOptions<TableResult<T>>.() -> Unit = {}
148174
): TableRequestHolder<T> {
149175
val options = UseTableRequestOptions<TableResult<T>>().apply(optionsOf)
@@ -154,45 +180,104 @@ fun <T> useTableRequest(
154180
val currentPage = pageState.value
155181
val currentPageSize = pageSizeState.value
156182

157-
// 2. Use refs for latest pagination values
183+
// 2. Sorting & filtering state
184+
val sortingState = _useState(options.initialSorting)
185+
val globalFilterState = _useState(options.initialGlobalFilter)
186+
val columnFiltersState = _useState(options.initialColumnFilters)
187+
188+
val currentSorting = sortingState.value
189+
val currentGlobalFilter = globalFilterState.value
190+
val currentColumnFilters = columnFiltersState.value
191+
192+
val sortingDeps = if (options.triggerOnSortingChange) currentSorting else null
193+
val globalFilterDep = if (options.triggerOnFilteringChange) currentGlobalFilter else null
194+
val columnFiltersDep = if (options.triggerOnFilteringChange) currentColumnFilters else null
195+
196+
// 3. Use refs for latest values
158197
val latestPage = useLatestRef(currentPage)
159198
val latestPageSize = useLatestRef(currentPageSize)
199+
val latestSorting = useLatestRef(currentSorting)
200+
val latestGlobalFilter = useLatestRef(currentGlobalFilter)
201+
val latestColumnFilters = useLatestRef(currentColumnFilters)
202+
203+
val requestParams = TableRequestParams(
204+
page = latestPage.current,
205+
pageSize = latestPageSize.current,
206+
sorting = latestSorting.current,
207+
globalFilter = latestGlobalFilter.current,
208+
columnFilters = latestColumnFilters.current
209+
)
210+
211+
val requestParamsState = _useState(requestParams)
160212

161-
// 3. Use useRequest with manual mode
162-
val requestHolder = useRequest<PageParams, TableResult<T>>(
163-
requestFn = { params -> requestFn(params.page, params.pageSize) },
213+
// 4. Use useRequest with manual mode (force manual to avoid duplicate auto-run)
214+
val requestHolder = useRequest<TableRequestParams, TableResult<T>>(
215+
requestFn = requestFn,
164216
optionsOf = {
165-
manual = true
166-
defaultParams = PageParams(latestPage.current, latestPageSize.current)
167217
options.requestOptions(this)
168-
// Per-page caching: if user set cacheKey, use custom cache functions
169-
// to generate unique keys for each page
218+
manual = true
219+
defaultParams = requestParamsState.value
220+
221+
val customTableSetCache = options.setCache
222+
val customTableGetCache = options.getCache
223+
if (customTableSetCache != null) {
224+
setCache = { cachedData ->
225+
customTableSetCache(cachedData)
226+
}
227+
}
228+
if (customTableGetCache != null) {
229+
getCache = { params ->
230+
customTableGetCache(params)
231+
}
232+
}
233+
170234
if (cacheKey.isNotEmpty()) {
171235
val baseCacheKey = cacheKey
236+
val mergeFullKey = options.mergeCacheKey
237+
val resolveKey: (TableRequestParams) -> String = { params ->
238+
mergeFullKey?.invoke(baseCacheKey, params)
239+
?: "${baseCacheKey}_p${params.page}_s${params.pageSize}"
240+
}
241+
val fallbackSetCache = setCache
242+
val fallbackGetCache = getCache
172243
setCache = { cachedData ->
173-
val params = cachedData.params as? PageParams
244+
val params = cachedData.params as? TableRequestParams
174245
val key = if (params != null) {
175-
"${baseCacheKey}_p${params.page}_s${params.pageSize}"
246+
resolveKey(params)
176247
} else {
177-
baseCacheKey
248+
resolveKey(requestParamsState.value)
178249
}
179250
CacheManager.saveCache(key, cacheTime, cachedData)
251+
fallbackSetCache?.invoke(cachedData)
180252
}
181253
getCache = { params ->
182-
val key = "${baseCacheKey}_p${params.page}_s${params.pageSize}"
254+
val key = resolveKey(params)
183255
CacheManager.getCache<TableResult<T>>(key)
256+
?: fallbackGetCache?.invoke(params)
184257
}
185258
}
186259
}
187260
)
188261

189-
// 4. Auto-fetch when pagination changes
190-
useEffect(currentPage, currentPageSize) {
191-
val pageParams = PageParams(latestPage.current, latestPageSize.current)
192-
requestHolder.request(pageParams)
262+
val refresh: () -> Unit = {
263+
requestHolder.request(requestParamsState.value)
193264
}
194265

195-
// 5. Extract rows and total from TableResult (now plain data, wrap in State at Holder level)
266+
// 5. Auto-fetch when pagination/sorting/filtering changes
267+
useEffect(currentPage, currentPageSize, sortingDeps, globalFilterDep, columnFiltersDep) {
268+
val newParams = TableRequestParams(
269+
page = latestPage.current,
270+
pageSize = latestPageSize.current,
271+
sorting = latestSorting.current,
272+
globalFilter = latestGlobalFilter.current,
273+
columnFilters = latestColumnFilters.current
274+
)
275+
requestParamsState.value = newParams
276+
options.onRequestParams?.invoke(newParams)
277+
requestHolder.request(newParams)
278+
}
279+
280+
// 6. Extract rows and total from TableResult (now plain data, wrap in State at Holder level)
196281
val rows = remember(requestHolder.data.value) {
197282
derivedStateOf {
198283
requestHolder.data.value?.rows ?: emptyList()
@@ -205,7 +290,7 @@ fun <T> useTableRequest(
205290
}
206291
}
207292

208-
// 6. Pagination controls
293+
// 7. Pagination controls
209294
val onPageChange: (Int, Int) -> Unit = { newPage, newSize ->
210295
if (newSize != currentPageSize) {
211296
pageSizeState.value = newSize
@@ -215,23 +300,63 @@ fun <T> useTableRequest(
215300
}
216301
}
217302

218-
// 7. Return holder
303+
// 8. Sorting & filtering controls
304+
val setSorting: (List<SortDescriptor>) -> Unit = { sorting ->
305+
sortingState.value = sorting
306+
}
307+
308+
val setGlobalFilter: (String) -> Unit = { filter ->
309+
globalFilterState.value = filter
310+
}
311+
312+
val setColumnFilter: (String, Any?) -> Unit = { columnId, value ->
313+
columnFiltersState.value = columnFiltersState.value + (columnId to value)
314+
}
315+
316+
val clearFilters: () -> Unit = {
317+
columnFiltersState.value = emptyMap()
318+
globalFilterState.value = ""
319+
}
320+
321+
// 9. Return holder
219322
return TableRequestHolder(
220323
rows = rows,
221324
isLoading = requestHolder.isLoading,
222325
error = requestHolder.error,
223-
refresh = requestHolder.refresh,
224-
cancel = requestHolder.cancel,
225326
currentPage = pageState,
226327
pageSize = pageSizeState,
227328
total = total,
228-
onPageChange = onPageChange
329+
sorting = sortingState,
330+
globalFilter = globalFilterState,
331+
columnFilters = columnFiltersState,
332+
refresh = refresh,
333+
cancel = requestHolder.cancel,
334+
onPageChange = onPageChange,
335+
setSorting = setSorting,
336+
setGlobalFilter = setGlobalFilter,
337+
setColumnFilter = setColumnFilter,
338+
clearFilters = clearFilters
229339
)
230340
}
231341

232342
/**
233343
* Alias for [useTableRequest] following Compose naming convention.
234344
*/
345+
@Composable
346+
fun <T> rememberTableRequest(
347+
requestFn: suspend (params: TableRequestParams) -> TableResult<T>,
348+
optionsOf: UseTableRequestOptions<TableResult<T>>.() -> Unit = {}
349+
): TableRequestHolder<T> = useTableRequest(requestFn, optionsOf)
350+
351+
@Composable
352+
fun <T> useTableRequest(
353+
requestFn: suspend (page: Int, pageSize: Int) -> TableResult<T>,
354+
optionsOf: UseTableRequestOptions<TableResult<T>>.() -> Unit = {}
355+
): TableRequestHolder<T> = useTableRequest(
356+
requestFn = { params: TableRequestParams -> requestFn(params.page, params.pageSize) },
357+
optionsOf = optionsOf
358+
)
359+
235360
@Composable
236361
fun <T> rememberTableRequest(
237362
requestFn: suspend (page: Int, pageSize: Int) -> TableResult<T>,

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/utils/ext.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,5 +307,3 @@ fun String.isEmail(): Boolean {
307307
)
308308
return emailPattern.matches(this)
309309
}
310-
311-

0 commit comments

Comments
 (0)