Skip to content

Commit 1aa38db

Browse files
committed
Add custom Slice type and tighten bindSelector types
1 parent f39d28a commit 1aa38db

File tree

9 files changed

+42
-61
lines changed

9 files changed

+42
-61
lines changed

src/index.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
11
import { useSlice } from './useSlice'
22

33
export default useSlice
4-
export type {
5-
SliceActions,
6-
SliceName,
7-
SliceSelectors,
8-
SliceState,
9-
UseSliceReturn
10-
} from './useSlice.types'

src/test/createMockSlice/createMockSlice.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { assoc } from '../../utils'
2+
import type { Slice } from '../../useSlice.types'
23
import type {
34
Config,
4-
MockSlice,
55
MockValues,
66
MockSliceReturn
77
} from './createMockSlice.types'
88

99
export function createMockSlice(config: Config) {
1010
const implementations: Record<string, Record<string, any>> = {}
1111

12-
function mockSlice<T extends MockSlice>(
12+
function mockSlice<T extends Slice>(
1313
mockedSlice: T,
1414
mockValues: Partial<MockValues<T>> = {}
1515
): MockSliceReturn<T> {
Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
1-
import { Mock } from 'vitest'
2-
import { Slice } from '@reduxjs/toolkit'
3-
import { SliceState, SliceSelectors, SliceActions } from '../../useSlice.types'
1+
import type { Mock } from 'vitest'
2+
import type {
3+
Slice,
4+
SliceState,
5+
SliceSelectors,
6+
SliceActions
7+
} from '../../useSlice.types'
8+
import type { OmitFirst } from '../../utils/utils.types'
49

510
export interface Config {
611
createSpy(): any
712
mockImplementation(implementations: any): void
813
}
914

10-
export type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never
11-
12-
export type MockSlice = Omit<Slice, 'selectors'> & {
13-
selectors: {
14-
[key: string]: <T extends Slice>(
15-
state: SliceState<T>,
16-
...params: any[]
17-
) => any
18-
}
19-
}
20-
21-
export type MockValues<T extends MockSlice> = {
15+
export type MockValues<T extends Slice> = {
2216
[K in keyof SliceSelectors<T>]: (
2317
...params: OmitFirst<Parameters<SliceSelectors<T>[K]>>
2418
) => SliceState<T>
2519
} & { state: SliceState<T> }
2620

27-
export type MockSliceReturn<T extends MockSlice> = {
21+
export type MockSliceReturn<T extends Slice> = {
2822
[K in keyof SliceActions<T>]: Mock
2923
}

src/test/createMockSlice/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
export { createMockSlice } from './createMockSlice'
22
export type {
33
Config,
4-
MockSlice,
54
MockSliceReturn,
6-
MockValues,
7-
OmitFirst
5+
MockValues
86
} from './createMockSlice.types'

src/useSlice.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import { useMemo } from 'react'
22
import { useSelector, useDispatch } from 'react-redux'
3-
import { bindActionCreators, type Slice } from '@reduxjs/toolkit'
3+
import { bindActionCreators } from '@reduxjs/toolkit'
44
import { useLatest, bindSelectors } from './utils'
5-
import type { UseSliceReturn } from './useSlice.types'
5+
import type { Slice, UseSliceReturn } from './useSlice.types'
66

77
export function useSlice<T extends Slice>(slice: T): UseSliceReturn<T> {
8-
const state = useSelector(slice.selectSlice)
8+
const { selectSlice, actions, selectors, name } = slice
9+
const state = useSelector(selectSlice)
910
const stateRef = useLatest(state)
1011
const dispatch = useDispatch()
1112

12-
const actions = useMemo(
13-
() => bindActionCreators(slice.actions, dispatch),
14-
[slice.actions, dispatch]
13+
const boundActions = useMemo(
14+
() => bindActionCreators(actions, dispatch),
15+
[actions, dispatch]
1516
)
16-
const selectors = useMemo(
17-
() => bindSelectors(slice.selectors, slice.name, stateRef),
18-
[slice.selectors, slice.name, stateRef]
17+
const boundSelectors = useMemo(
18+
() => bindSelectors(selectors, name, stateRef),
19+
[selectors, name, stateRef]
1920
)
2021

21-
return [state, actions, selectors]
22+
return [state, boundActions, boundSelectors]
2223
}

src/useSlice.types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import type { Slice } from '@reduxjs/toolkit'
1+
import type { Slice as ToolkitSlice } from '@reduxjs/toolkit'
22
import type { BoundSelectors } from './utils'
33

4+
export type Slice = Omit<ToolkitSlice, 'selectors'> & {
5+
selectors: Record<string, (state: any, ...params: any[]) => any>
6+
}
7+
48
export type SliceState<T extends Slice> = ReturnType<T['selectSlice']>
59
export type SliceActions<T extends Slice> = T['actions']
610
export type SliceSelectors<T extends Slice> = T['selectors']

src/utils/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,2 @@
11
export { objectEntries, assoc, append, useLatest, bindSelectors } from './utils'
2-
export type {
3-
ObjectEntriesReturn,
4-
Expand,
5-
BoundSelector,
6-
BoundSelectors
7-
} from './utils.types'
2+
export type { ObjectEntriesReturn, Expand, BoundSelectors } from './utils.types'

src/utils/utils.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useRef, RefObject } from 'react'
2-
import { Slice } from '@reduxjs/toolkit'
3-
import { ObjectEntriesReturn, Expand, BoundSelectors } from './utils.types'
1+
import { useRef, type RefObject } from 'react'
2+
import type { Slice } from './../useSlice.types'
3+
import type { ObjectEntriesReturn, Expand, BoundSelectors } from './utils.types'
44

55
export function objectEntries<T extends Record<string, any>>(
66
obj: T
@@ -36,11 +36,11 @@ export function bindSelectors<
3636
K extends Slice['name'],
3737
V extends RefObject<ReturnType<Slice['selectSlice']>>
3838
>(selectors: T, sliceName: K, stateRef: V) {
39-
return objectEntries(selectors).reduce<BoundSelectors<any>>(
40-
(accumulator, [name, fn]: [any, any]) =>
39+
return objectEntries(selectors).reduce(
40+
(accumulator, [name, fn]) =>
4141
assoc(accumulator, name, (...rest: any[]) =>
4242
fn({ [sliceName]: stateRef.current }, ...rest)
4343
),
44-
{}
44+
{} as BoundSelectors<T>
4545
)
4646
}

src/utils/utils.types.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1+
import type { Slice } from '../useSlice.types'
2+
13
export type ObjectEntriesReturn<T> = {
24
[K in keyof T]: [K, T[K]]
35
}[keyof T][] & {}
46

57
export type Expand<T> = { [P in keyof T]: T[P] } & {}
68

7-
export type BoundSelector<T extends (...args: any[]) => any> = T extends (
8-
state: any,
9-
...args: infer Args
10-
) => infer Return
11-
? (...args: Args) => Return
12-
: never
9+
export type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never
1310

14-
export type BoundSelectors<T extends Record<string, (...args: any[]) => any>> =
15-
{
16-
[P in keyof T]: BoundSelector<T[P]>
17-
}
11+
export type BoundSelectors<T extends Slice['selectors']> = {
12+
[K in keyof T]: (...params: OmitFirst<Parameters<T[K]>>) => ReturnType<T[K]>
13+
}

0 commit comments

Comments
 (0)