react-hook-tanstack-table 0.0.1
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.
- package/LICENSE +21 -0
- package/README.org +29 -0
- package/dist/TableContext.cjs +6 -0
- package/dist/TableContext.d.cts +7 -0
- package/dist/TableContext.d.cts.map +1 -0
- package/dist/TableContext.d.ts +7 -0
- package/dist/TableContext.d.ts.map +1 -0
- package/dist/TableContext.js +8 -0
- package/dist/TableContext.js.map +1 -0
- package/dist/hasTableArg-Bu3FbcSo.js +8 -0
- package/dist/hasTableArg-Bu3FbcSo.js.map +1 -0
- package/dist/hasTableArg-dMtayKWH.cjs +11 -0
- package/dist/index.cjs +40 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/invariant-BgWq7zZS.js +12 -0
- package/dist/invariant-BgWq7zZS.js.map +1 -0
- package/dist/invariant-D8b385d_.cjs +21 -0
- package/dist/isShallowEqual-D6PQ041u.cjs +36 -0
- package/dist/isShallowEqual-Kynsoe7a.js +33 -0
- package/dist/isShallowEqual-Kynsoe7a.js.map +1 -0
- package/dist/runGetters-BfRTE7LZ.js +16 -0
- package/dist/runGetters-BfRTE7LZ.js.map +1 -0
- package/dist/runGetters-Q-UsPG2j.cjs +19 -0
- package/dist/types-DIWB6wdO.d.cts +11 -0
- package/dist/types-DIWB6wdO.d.cts.map +1 -0
- package/dist/types-DIWB6wdO.d.ts +11 -0
- package/dist/types-DIWB6wdO.d.ts.map +1 -0
- package/dist/useCell.cjs +53 -0
- package/dist/useCell.d.cts +17 -0
- package/dist/useCell.d.cts.map +1 -0
- package/dist/useCell.d.ts +17 -0
- package/dist/useCell.d.ts.map +1 -0
- package/dist/useCell.js +53 -0
- package/dist/useCell.js.map +1 -0
- package/dist/useColumn.cjs +54 -0
- package/dist/useColumn.d.cts +19 -0
- package/dist/useColumn.d.cts.map +1 -0
- package/dist/useColumn.d.ts +19 -0
- package/dist/useColumn.d.ts.map +1 -0
- package/dist/useColumn.js +54 -0
- package/dist/useColumn.js.map +1 -0
- package/dist/useHeader.cjs +48 -0
- package/dist/useHeader.d.cts +19 -0
- package/dist/useHeader.d.cts.map +1 -0
- package/dist/useHeader.d.ts +19 -0
- package/dist/useHeader.d.ts.map +1 -0
- package/dist/useHeader.js +48 -0
- package/dist/useHeader.js.map +1 -0
- package/dist/useReactTable-Y2z5JrT_.js +70 -0
- package/dist/useReactTable-Y2z5JrT_.js.map +1 -0
- package/dist/useReactTable-nxQRPiMU.cjs +95 -0
- package/dist/useReactTable.cjs +3 -0
- package/dist/useReactTable.d.cts +10 -0
- package/dist/useReactTable.d.cts.map +1 -0
- package/dist/useReactTable.d.ts +10 -0
- package/dist/useReactTable.d.ts.map +1 -0
- package/dist/useReactTable.js +2 -0
- package/dist/useRow.cjs +54 -0
- package/dist/useRow.d.cts +19 -0
- package/dist/useRow.d.cts.map +1 -0
- package/dist/useRow.d.ts +19 -0
- package/dist/useRow.d.ts.map +1 -0
- package/dist/useRow.js +54 -0
- package/dist/useRow.js.map +1 -0
- package/dist/useTable.cjs +44 -0
- package/dist/useTable.d.cts +11 -0
- package/dist/useTable.d.cts.map +1 -0
- package/dist/useTable.d.ts +11 -0
- package/dist/useTable.d.ts.map +1 -0
- package/dist/useTable.js +44 -0
- package/dist/useTable.js.map +1 -0
- package/dist/useTableWithSelector-Bdsf6Zxk.cjs +79 -0
- package/dist/useTableWithSelector-D3f689pF.js +70 -0
- package/dist/useTableWithSelector-D3f689pF.js.map +1 -0
- package/dist/useTableWithSelector.cjs +3 -0
- package/dist/useTableWithSelector.d.cts +8 -0
- package/dist/useTableWithSelector.d.cts.map +1 -0
- package/dist/useTableWithSelector.d.ts +8 -0
- package/dist/useTableWithSelector.d.ts.map +1 -0
- package/dist/useTableWithSelector.js +2 -0
- package/package.json +99 -0
- package/src/contexts/TableContext.ts +7 -0
- package/src/hooks/useCell.ts +156 -0
- package/src/hooks/useColumn.ts +168 -0
- package/src/hooks/useHeader.ts +151 -0
- package/src/hooks/useReactTable.ts +88 -0
- package/src/hooks/useRow.ts +132 -0
- package/src/hooks/useTable.ts +113 -0
- package/src/hooks/useTableWithSelector.ts +143 -0
- package/src/index.ts +13 -0
- package/src/lib/hasTableArg.ts +19 -0
- package/src/lib/identity.ts +1 -0
- package/src/lib/invariant.ts +12 -0
- package/src/lib/isShallowEqual.ts +77 -0
- package/src/lib/runGetters.ts +17 -0
- package/src/lib/tableRegistry.ts +3 -0
- package/src/lib/uncapitalize.ts +2 -0
- package/src/lib/useLayoutEffect.ts +3 -0
- package/src/lib/useShallowMemo.ts +12 -0
- package/src/types.ts +24 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useCallback } from "react"
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
RequiredKeys,
|
|
5
|
+
RowData,
|
|
6
|
+
Table,
|
|
7
|
+
TableOptionsResolved,
|
|
8
|
+
} from "@tanstack/table-core"
|
|
9
|
+
|
|
10
|
+
import { identity } from "../lib/identity"
|
|
11
|
+
import { runGetters } from "../lib/runGetters"
|
|
12
|
+
import { isShallowEqual } from "../lib/isShallowEqual"
|
|
13
|
+
|
|
14
|
+
import type { IsEqual, RunGetters } from "../types"
|
|
15
|
+
|
|
16
|
+
import { useTableWithSelector } from "./useTableWithSelector"
|
|
17
|
+
|
|
18
|
+
export interface TableValues<TData extends RowData> extends RunGetters<
|
|
19
|
+
Table<TData>
|
|
20
|
+
> {}
|
|
21
|
+
|
|
22
|
+
const tableValuesCache = new WeakMap<
|
|
23
|
+
RequiredKeys<TableOptionsResolved<any>, "state">,
|
|
24
|
+
TableValues<any>
|
|
25
|
+
>()
|
|
26
|
+
|
|
27
|
+
const tableHandlersCache = new WeakMap<
|
|
28
|
+
Table<any>,
|
|
29
|
+
{
|
|
30
|
+
toggleAllColumnsVisibilityHandler: ReturnType<
|
|
31
|
+
Table<any>["getToggleAllColumnsVisibilityHandler"]
|
|
32
|
+
>
|
|
33
|
+
toggleAllPageRowsSelectedHandler: ReturnType<
|
|
34
|
+
Table<any>["getToggleAllPageRowsSelectedHandler"]
|
|
35
|
+
>
|
|
36
|
+
toggleAllRowsExpandedHandler: ReturnType<
|
|
37
|
+
Table<any>["getToggleAllRowsExpandedHandler"]
|
|
38
|
+
>
|
|
39
|
+
toggleAllRowsSelectedHandler: ReturnType<
|
|
40
|
+
Table<any>["getToggleAllRowsSelectedHandler"]
|
|
41
|
+
>
|
|
42
|
+
}
|
|
43
|
+
>()
|
|
44
|
+
|
|
45
|
+
const getTableValues = <TData extends RowData>(
|
|
46
|
+
table: Table<TData>,
|
|
47
|
+
): TableValues<TData> => {
|
|
48
|
+
let cached = tableValuesCache.get(table.options)
|
|
49
|
+
if (!cached) {
|
|
50
|
+
let cachedHandlers = tableHandlersCache.get(table)
|
|
51
|
+
if (!cachedHandlers) {
|
|
52
|
+
cachedHandlers = {
|
|
53
|
+
toggleAllColumnsVisibilityHandler:
|
|
54
|
+
table.getToggleAllColumnsVisibilityHandler(),
|
|
55
|
+
toggleAllPageRowsSelectedHandler:
|
|
56
|
+
table.getToggleAllPageRowsSelectedHandler(),
|
|
57
|
+
toggleAllRowsExpandedHandler: table.getToggleAllRowsExpandedHandler(),
|
|
58
|
+
toggleAllRowsSelectedHandler: table.getToggleAllRowsSelectedHandler(),
|
|
59
|
+
}
|
|
60
|
+
tableHandlersCache.set(table, cachedHandlers)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const {
|
|
64
|
+
toggleAllColumnsVisibilityHandler,
|
|
65
|
+
toggleAllPageRowsSelectedHandler,
|
|
66
|
+
toggleAllRowsExpandedHandler,
|
|
67
|
+
toggleAllRowsSelectedHandler,
|
|
68
|
+
} = cachedHandlers
|
|
69
|
+
|
|
70
|
+
const { getColumn, getRow, ...rest } = table
|
|
71
|
+
|
|
72
|
+
cached = {
|
|
73
|
+
...runGetters(rest),
|
|
74
|
+
getColumn,
|
|
75
|
+
getRow,
|
|
76
|
+
toggleAllColumnsVisibilityHandler,
|
|
77
|
+
toggleAllPageRowsSelectedHandler,
|
|
78
|
+
toggleAllRowsExpandedHandler,
|
|
79
|
+
toggleAllRowsSelectedHandler,
|
|
80
|
+
}
|
|
81
|
+
tableValuesCache.set(table.options, cached)
|
|
82
|
+
}
|
|
83
|
+
return cached
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type Selector<TData extends RowData, Selection> = (
|
|
87
|
+
tableValues: TableValues<TData>,
|
|
88
|
+
) => Selection
|
|
89
|
+
|
|
90
|
+
export const useTable = <TData extends RowData, Selection = TableValues<TData>>(
|
|
91
|
+
table?: Table<TData> | undefined,
|
|
92
|
+
selector: Selector<TData, Selection> = identity as never,
|
|
93
|
+
isEqual: IsEqual<NoInfer<Selection>> = isShallowEqual,
|
|
94
|
+
): Selection => {
|
|
95
|
+
const getSelection = useCallback(
|
|
96
|
+
(table: Table<TData>) => selector(getTableValues(table)),
|
|
97
|
+
[selector],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return useTableWithSelector(table, getSelection, isEqual)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const tableHook = useTable
|
|
104
|
+
|
|
105
|
+
const tableHookʹ =
|
|
106
|
+
<TData extends RowData>(table?: Table<TData> | undefined) =>
|
|
107
|
+
<Selection = TableValues<TData>>(
|
|
108
|
+
selector: Selector<TData, Selection> = identity as never,
|
|
109
|
+
isEqual: IsEqual<NoInfer<Selection>> = isShallowEqual,
|
|
110
|
+
): Selection =>
|
|
111
|
+
tableHook(table, selector, isEqual)
|
|
112
|
+
|
|
113
|
+
export { tableHookʹ as useTableʹ }
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
use,
|
|
3
|
+
useCallback,
|
|
4
|
+
useInsertionEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useSyncExternalStore,
|
|
8
|
+
} from "react"
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
RequiredKeys,
|
|
12
|
+
RowData,
|
|
13
|
+
Table,
|
|
14
|
+
TableOptionsResolved,
|
|
15
|
+
} from "@tanstack/table-core"
|
|
16
|
+
|
|
17
|
+
import { TableContext } from "../contexts/TableContext"
|
|
18
|
+
|
|
19
|
+
import { identity } from "../lib/identity"
|
|
20
|
+
import { tableRegistry } from "../lib/tableRegistry"
|
|
21
|
+
import { invariant } from "../lib/invariant"
|
|
22
|
+
|
|
23
|
+
import type { IsEqual } from "../types"
|
|
24
|
+
|
|
25
|
+
export const useTableWithSelector = <
|
|
26
|
+
TData extends RowData,
|
|
27
|
+
Selection = Table<TData>,
|
|
28
|
+
>(
|
|
29
|
+
table?: Table<TData> | undefined,
|
|
30
|
+
selector: (table: Table<TData>) => Selection = identity as never,
|
|
31
|
+
isEqual?: IsEqual<NoInfer<Selection>> | undefined,
|
|
32
|
+
): Selection => {
|
|
33
|
+
"use no memo"
|
|
34
|
+
|
|
35
|
+
const resolvedTable = table ?? use<Table<TData> | null>(TableContext)
|
|
36
|
+
|
|
37
|
+
invariant(
|
|
38
|
+
resolvedTable,
|
|
39
|
+
"`useTableWithSelector` must be used as a descendent of `TableContext` or provided with a `Table` as an argument!",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const listeners = useMemo(
|
|
43
|
+
() => (tableRegistry.get(resolvedTable) ?? {}).listeners,
|
|
44
|
+
[resolvedTable],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
invariant(
|
|
48
|
+
listeners,
|
|
49
|
+
"The provided/found `Table` instance cannot be subscribed to. Was it created by the correct `useReactTable` hook?",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const subscribe = useCallback(
|
|
53
|
+
(onTableChange: () => void) => {
|
|
54
|
+
listeners.add(onTableChange)
|
|
55
|
+
return () => {
|
|
56
|
+
listeners.delete(onTableChange)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
[listeners],
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
// Use this to track the rendered snapshot.
|
|
63
|
+
const instRef = useRef<
|
|
64
|
+
| {
|
|
65
|
+
hasValue: false
|
|
66
|
+
value: null
|
|
67
|
+
}
|
|
68
|
+
| { hasValue: true; value: Selection }
|
|
69
|
+
>({
|
|
70
|
+
hasValue: false,
|
|
71
|
+
value: null,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
75
|
+
const getSnapshot = useMemo(() => {
|
|
76
|
+
let hasMemo = false,
|
|
77
|
+
memoizedTableOptions: RequiredKeys<TableOptionsResolved<TData>, "state">,
|
|
78
|
+
memoizedSelection: Selection
|
|
79
|
+
|
|
80
|
+
return () => {
|
|
81
|
+
const nextTableOptions = resolvedTable.options
|
|
82
|
+
if (!hasMemo) {
|
|
83
|
+
// The first time the hook is called, there is no memoized result.
|
|
84
|
+
// eslint-disable-next-line react-hooks/immutability -- should be fine 😅 this is how it is in the upstream, too
|
|
85
|
+
hasMemo = true
|
|
86
|
+
memoizedTableOptions = nextTableOptions
|
|
87
|
+
const nextSelection = selector(resolvedTable)
|
|
88
|
+
|
|
89
|
+
if (isEqual !== undefined) {
|
|
90
|
+
// Even if the selector has changed, the currently rendered selection
|
|
91
|
+
// may be equal to the new selection. We should attempt to reuse the
|
|
92
|
+
// current value if possible, to preserve downstream memoizations.
|
|
93
|
+
if (instRef.current.hasValue) {
|
|
94
|
+
const currentSelection = instRef.current.value
|
|
95
|
+
if (isEqual(currentSelection, nextSelection)) {
|
|
96
|
+
memoizedSelection = currentSelection
|
|
97
|
+
return currentSelection
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
memoizedSelection = nextSelection
|
|
103
|
+
return nextSelection
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const prevTableOptions = memoizedTableOptions
|
|
107
|
+
// eslint-disable-next-line react-hooks/memo-dependencies
|
|
108
|
+
const prevSelection = memoizedSelection
|
|
109
|
+
|
|
110
|
+
if (Object.is(prevTableOptions, nextTableOptions)) {
|
|
111
|
+
// The snapshot is the same as last time. Reuse the previous selection.
|
|
112
|
+
return prevSelection
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The snapshot has changed, so we need to compute a new selection.
|
|
116
|
+
const nextSelection = selector(resolvedTable)
|
|
117
|
+
|
|
118
|
+
// If a custom isEqual function is provided, use that to check if the data
|
|
119
|
+
// has changed. If it hasn't, return the previous selection. That signals
|
|
120
|
+
// to React that the selections are conceptually equal, and we can bail
|
|
121
|
+
// out of rendering.
|
|
122
|
+
if (isEqual !== undefined && isEqual(prevSelection, nextSelection)) {
|
|
123
|
+
// The snapshot still has changed, so make sure to update to not keep
|
|
124
|
+
// old references alive
|
|
125
|
+
memoizedTableOptions = nextTableOptions
|
|
126
|
+
return prevSelection
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
memoizedTableOptions = nextTableOptions
|
|
130
|
+
memoizedSelection = nextSelection
|
|
131
|
+
return nextSelection
|
|
132
|
+
}
|
|
133
|
+
}, [selector, resolvedTable, isEqual])
|
|
134
|
+
|
|
135
|
+
const value = useSyncExternalStore(subscribe, getSnapshot)
|
|
136
|
+
|
|
137
|
+
useInsertionEffect(() => {
|
|
138
|
+
instRef.current.hasValue = true
|
|
139
|
+
instRef.current.value = value
|
|
140
|
+
}, [value])
|
|
141
|
+
|
|
142
|
+
return value
|
|
143
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from '@tanstack/table-core'
|
|
2
|
+
export * from '@tanstack/react-table'
|
|
3
|
+
|
|
4
|
+
export { TableContext } from "./contexts/TableContext"
|
|
5
|
+
|
|
6
|
+
export { useReactTable } from "./hooks/useReactTable"
|
|
7
|
+
|
|
8
|
+
export { useTableWithSelector } from './hooks/useTableWithSelector'
|
|
9
|
+
export { useTable, useTableʹ } from "./hooks/useTable"
|
|
10
|
+
export { useHeader, useHeaderʹ } from "./hooks/useHeader"
|
|
11
|
+
export { useColumn, useColumnʹ } from "./hooks/useColumn"
|
|
12
|
+
export { useRow, useRowʹ } from "./hooks/useRow"
|
|
13
|
+
export { useCell, useCellʹ } from "./hooks/useCell"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RowData,
|
|
3
|
+
Table,
|
|
4
|
+
} from "@tanstack/table-core"
|
|
5
|
+
|
|
6
|
+
import { tableRegistry } from "./tableRegistry"
|
|
7
|
+
|
|
8
|
+
const isTable = <TData extends RowData>(
|
|
9
|
+
x: unknown | Table<TData>,
|
|
10
|
+
): x is Table<TData> =>
|
|
11
|
+
typeof x === "object" && x !== null && tableRegistry.has(x as Table<TData>)
|
|
12
|
+
|
|
13
|
+
export const hasTableArg = <
|
|
14
|
+
TData extends RowData,
|
|
15
|
+
Rest extends [{}, ...unknown[]],
|
|
16
|
+
>(
|
|
17
|
+
args: [Table<TData> | undefined, ...Rest] | Rest,
|
|
18
|
+
): args is [Table<TData> | undefined, ...Rest] =>
|
|
19
|
+
isTable(args[0]) || args[0] === undefined
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const identity = <T>(x: T, ..._: any[]): T => x
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function invariant(
|
|
2
|
+
condition: any,
|
|
3
|
+
message?: string | (() => string) | undefined
|
|
4
|
+
): asserts condition {
|
|
5
|
+
if (condition) return
|
|
6
|
+
throw new Error(
|
|
7
|
+
[
|
|
8
|
+
'Invariant failed',
|
|
9
|
+
typeof message === 'function' ? message() : message
|
|
10
|
+
].filter(Boolean).join(': ')
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const isMapShallowEqual = (
|
|
2
|
+
a: ReadonlyMap<unknown, unknown>,
|
|
3
|
+
b: ReadonlyMap<unknown, unknown>,
|
|
4
|
+
): boolean => {
|
|
5
|
+
if (a.size !== b.size) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
for (const [key, value] of a) {
|
|
10
|
+
const valueB = b.get(key);
|
|
11
|
+
if (value !== valueB || !Object.is(value, valueB)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isSetShallowEqual = (
|
|
20
|
+
a: ReadonlySet<unknown>,
|
|
21
|
+
b: ReadonlySet<unknown>,
|
|
22
|
+
): boolean => {
|
|
23
|
+
if (a.size !== b.size) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const value of a) {
|
|
28
|
+
if (!b.has(value)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const isShallowEqual = <T> (a: T, b: T): boolean => {
|
|
37
|
+
if (a === b || Object.is(a, b)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
typeof a !== "object" ||
|
|
43
|
+
a === null ||
|
|
44
|
+
typeof b !== "object" ||
|
|
45
|
+
b === null
|
|
46
|
+
) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (a instanceof Map && b instanceof Map) {
|
|
51
|
+
return isMapShallowEqual(a, b);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (a instanceof Set && b instanceof Set) {
|
|
55
|
+
return isSetShallowEqual(a, b);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const keys = Object.keys(a);
|
|
59
|
+
if (keys.length !== Object.keys(b).length) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const key of keys) {
|
|
64
|
+
if (!Object.hasOwn(b, key)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { [key as keyof T]: valueA } = a;
|
|
69
|
+
const { [key as keyof T]: valueB } = b;
|
|
70
|
+
|
|
71
|
+
if (valueA !== valueB || !Object.is(valueA, valueB)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RunGetters } from "../types"
|
|
2
|
+
import { uncapitalize } from "./uncapitalize"
|
|
3
|
+
|
|
4
|
+
export const runGetters = <T extends object>(x: T): RunGetters<T> =>
|
|
5
|
+
Object.fromEntries(
|
|
6
|
+
Object.entries(x).map(([key, value]) => {
|
|
7
|
+
if (typeof value === "function" && key.startsWith("get")) {
|
|
8
|
+
try {
|
|
9
|
+
return [uncapitalize(key.replace(/^get/, "")), value()]
|
|
10
|
+
} catch {
|
|
11
|
+
return [key, value]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return [key, value]
|
|
16
|
+
}),
|
|
17
|
+
) as RunGetters<T>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { isShallowEqual } from "./isShallowEqual"
|
|
3
|
+
|
|
4
|
+
export const useShallowMemo = <T>(next: T): T => {
|
|
5
|
+
const [current, setCurrent] = useState(next)
|
|
6
|
+
|
|
7
|
+
if (!isShallowEqual(current, next)) {
|
|
8
|
+
setCurrent(next)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return current
|
|
12
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Simplify } from 'type-fest'
|
|
2
|
+
|
|
3
|
+
export type IsEqual<Selection> = (prev: Selection, next: Selection) => boolean
|
|
4
|
+
|
|
5
|
+
type GetterKeys<T> = {
|
|
6
|
+
[k in keyof T]-?: k extends `get${string}` ?
|
|
7
|
+
T[k] extends () => unknown ?
|
|
8
|
+
k
|
|
9
|
+
: never
|
|
10
|
+
: never
|
|
11
|
+
}[keyof T]
|
|
12
|
+
|
|
13
|
+
type GottenKey<k extends string> =
|
|
14
|
+
k extends `get${infer K}` ? Uncapitalize<K> : never
|
|
15
|
+
|
|
16
|
+
type GetterReturn<T> = T extends () => unknown ? ReturnType<T> : never
|
|
17
|
+
|
|
18
|
+
export type RunGetters<T> = Simplify<
|
|
19
|
+
{
|
|
20
|
+
[k in GetterKeys<T> as GottenKey<k>]: GetterReturn<T[k]>
|
|
21
|
+
} & {
|
|
22
|
+
[k in Exclude<keyof T, GetterKeys<T>>]: T[k]
|
|
23
|
+
}
|
|
24
|
+
>
|