Skip to content

Commit 07d5178

Browse files
Improve-Type-Safety-and-State-Access-in-useStateWithDeps-Hook (#3027)
Co-authored-by: Jiachi Liu <[email protected]>
1 parent 076a538 commit 07d5178

File tree

2 files changed

+26
-16
lines changed

2 files changed

+26
-16
lines changed

src/mutation/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,16 @@ const mutation = (<Data, Error>() =>
3333
// Ditch all mutation results that happened earlier than this timestamp.
3434
const ditchMutationsUntilRef = useRef(0)
3535

36-
const [stateRef, stateDependencies, setState] = useStateWithDeps({
36+
const [stateRef, stateDependencies, setState] = useStateWithDeps<{
37+
data: Data | undefined
38+
error: Error | undefined
39+
isMutating: boolean
40+
}>({
3741
data: UNDEFINED,
3842
error: UNDEFINED,
3943
isMutating: false
4044
})
45+
4146
const currentState = stateRef.current
4247

4348
const trigger = useCallback(

src/mutation/state.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ export const startTransition: (scope: TransitionFunction) => void =
1111

1212
/**
1313
* An implementation of state with dependency-tracking.
14+
* @param initialState - The initial state object.
1415
*/
15-
export const useStateWithDeps = <S = any>(
16-
state: any
16+
export const useStateWithDeps = <S = Record<string, any>>(
17+
initialState: S
1718
): [
18-
MutableRefObject<any>,
19+
MutableRefObject<S>,
1920
Record<keyof S, boolean>,
2021
(payload: Partial<S>) => void
2122
] => {
2223
const [, rerender] = useState<Record<string, unknown>>({})
2324
const unmountedRef = useRef(false)
24-
const stateRef = useRef(state)
25+
const stateRef = useRef<S>(initialState)
2526

2627
// If a state property (data, error, or isValidating) is accessed by the render
2728
// function, we mark the property as a dependency so if it is updated again
@@ -31,9 +32,10 @@ export const useStateWithDeps = <S = any>(
3132
data: false,
3233
error: false,
3334
isValidating: false
34-
} as any)
35+
} as Record<keyof S, boolean>)
3536

3637
/**
38+
* Updates state and triggers re-render if necessary.
3739
* @param payload To change stateRef, pass the values explicitly to setState:
3840
* @example
3941
* ```js
@@ -54,18 +56,21 @@ export const useStateWithDeps = <S = any>(
5456
let shouldRerender = false
5557

5658
const currentState = stateRef.current
57-
for (const _ in payload) {
58-
const k = _ as keyof S
59+
for (const key in payload) {
60+
if (Object.prototype.hasOwnProperty.call(payload, key)) {
61+
const k = key as keyof S
5962

60-
// If the property has changed, update the state and mark rerender as
61-
// needed.
62-
if (currentState[k] !== payload[k]) {
63-
currentState[k] = payload[k]
63+
// If the property has changed, update the state and mark rerender as
64+
// needed.
65+
if (currentState[k] !== payload[k]) {
66+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
67+
currentState[k] = payload[k]!
6468

65-
// If the property is accessed by the component, a rerender should be
66-
// triggered.
67-
if (stateDependenciesRef.current[k]) {
68-
shouldRerender = true
69+
// If the property is accessed by the component, a rerender should be
70+
// triggered.
71+
if (stateDependenciesRef.current[k]) {
72+
shouldRerender = true
73+
}
6974
}
7075
}
7176
}

0 commit comments

Comments
 (0)