Skip to content

Commit 8de29f0

Browse files
committed
fix: dispatches in watchEffect should no longer trigger when selector is changed
1 parent cd41c34 commit 8de29f0

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

Diff for: examples/vue/simple/src/App.vue

+5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
import { useSelector, useDispatch } from '@reduxjs/vue-redux'
33
import { decrement, increment } from './store/counter-slice'
44
import { RootState } from './store'
5+
import { watchEffect } from 'vue'
56
67
const count = useSelector((state: RootState) => state.counter.value)
78
const dispatch = useDispatch()
9+
10+
watchEffect(() => {
11+
dispatch(increment())
12+
})
813
</script>
914

1015
<template>

Diff for: packages/vue-redux/src/compositions/use-selector.ts

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { shallowReadonly, shallowRef, toRaw, watch } from 'vue'
1+
import { shallowReadonly, watch } from 'vue'
22
import { ContextKey } from '../provider/context'
3+
import { useTracklessShallowRefWithEqualityCheck } from '../utils/use-trackless-shallow-ref-with-equality-check'
34
import {
45
createReduxContextComposition,
56
useReduxContext as useDefaultReduxContext,
@@ -93,18 +94,17 @@ export function createSelectorComposition(
9394

9495
// TODO: Introduce wrappedSelector for debuggability
9596

96-
const selectedState = shallowRef(selector(store.getState() as TState))
97+
const selectedState = useTracklessShallowRefWithEqualityCheck(
98+
selector(store.getState() as TState),
99+
equalityFn,
100+
)
97101

98102
watch(
99103
() => store,
100104
(_, __, onCleanup) => {
101105
const unsubscribe = subscription.addNestedSub(() => {
102106
const data = selector(store.getState() as TState)
103-
if (equalityFn(toRaw(selectedState.value) as Selected, data)) {
104-
return
105-
}
106-
107-
selectedState.value = data
107+
selectedState.ref.value = data
108108
})
109109

110110
onCleanup(() => {
@@ -113,10 +113,11 @@ export function createSelectorComposition(
113113
},
114114
{
115115
immediate: true,
116+
deep: false,
116117
},
117118
)
118119

119-
return shallowReadonly(selectedState)
120+
return shallowReadonly(selectedState.ref)
120121
}
121122

122123
Object.assign(useSelector, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { customRef } from 'vue'
2+
import type { Ref } from 'vue'
3+
import type { EqualityFn } from '../types'
4+
5+
/**
6+
* In some instances, `watch` will attempt to read the value from an inner ref,
7+
* even when not part of the `() => ...` function. This can cause unwanted
8+
* side effects to occur.
9+
*
10+
* To sidestep this, we can expose the inner value as an untracked property
11+
*/
12+
export function useTracklessShallowRefWithEqualityCheck<T>(
13+
initialValue: T,
14+
equalityFn: EqualityFn<T>,
15+
): {
16+
untrackedValue: { get(): T; set(_newValue: T): void }
17+
ref: Ref<T>
18+
} {
19+
let untrackedValue = initialValue
20+
return {
21+
// Avoid stale values by exposing the inner value as an untracked property
22+
untrackedValue: {
23+
get() {
24+
return untrackedValue
25+
},
26+
set(_newValue: T) {},
27+
},
28+
ref: customRef((track, trigger) => {
29+
return {
30+
get() {
31+
track()
32+
return untrackedValue
33+
},
34+
set(newValue: T) {
35+
if (equalityFn(untrackedValue, newValue)) {
36+
return
37+
}
38+
untrackedValue = newValue
39+
trigger()
40+
},
41+
}
42+
}),
43+
}
44+
}

0 commit comments

Comments
 (0)