Skip to content

Commit

Permalink
fix: dispatches in watchEffect should no longer trigger when selector…
Browse files Browse the repository at this point in the history
… is changed
  • Loading branch information
crutchcorn committed Dec 20, 2024
1 parent cd41c34 commit 20973fb
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 8 deletions.
5 changes: 5 additions & 0 deletions examples/vue/simple/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
import { useSelector, useDispatch } from '@reduxjs/vue-redux'
import { decrement, increment } from './store/counter-slice'
import { RootState } from './store'
import { watchEffect } from 'vue'
const count = useSelector((state: RootState) => state.counter.value)
const dispatch = useDispatch()
watchEffect(() => {
dispatch(increment())
})
</script>

<template>
Expand Down
17 changes: 9 additions & 8 deletions packages/vue-redux/src/compositions/use-selector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { shallowReadonly, shallowRef, toRaw, watch } from 'vue'
import { shallowReadonly, watch } from 'vue'
import { ContextKey } from '../provider/context'
import { useTracklessShallowRefWithEqualityCheck } from '../utils/use-trackless-shallow-ref-with-equality-check'
import {
createReduxContextComposition,
useReduxContext as useDefaultReduxContext,
Expand Down Expand Up @@ -93,18 +94,17 @@ export function createSelectorComposition(

// TODO: Introduce wrappedSelector for debuggability

const selectedState = shallowRef(selector(store.getState() as TState))
const selectedState = useTracklessShallowRefWithEqualityCheck(
selector(store.getState() as TState),
equalityFn,
)

watch(
() => store,
(_, __, onCleanup) => {
const unsubscribe = subscription.addNestedSub(() => {
const data = selector(store.getState() as TState)
if (equalityFn(toRaw(selectedState.value) as Selected, data)) {
return
}

selectedState.value = data
selectedState.ref.value = data
})

onCleanup(() => {
Expand All @@ -113,10 +113,11 @@ export function createSelectorComposition(
},
{
immediate: true,
deep: false,
},
)

return shallowReadonly(selectedState)
return shallowReadonly(selectedState.ref)
}

Object.assign(useSelector, {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { customRef } from 'vue'
import type { Ref } from 'vue'
import type { EqualityFn } from '../types'

/**
* In some instances, `watch` will attempt to read the value from an inner ref,
* even when not part of the `() => ...` function. This can cause unwanted
* side effects to occur.
*
* To sidestep this, we can expose the inner value as an untracked property
*/
export function useTracklessShallowRefWithEqualityCheck<T>(
initialValue: T,
equalityFn: EqualityFn<T>,
): {
untrackedValue: { get(): T; set(_newValue: T): void }
ref: Ref<T>
} {
let untrackedValue = initialValue
return {
// Avoid stale values by exposing the inner value as an untracked property
untrackedValue: {
get() {
return untrackedValue
},
set(_newValue: T) {},
},
ref: customRef((track, trigger) => {
return {
get() {
track()
return untrackedValue
},
set(newValue: T) {
if (equalityFn(untrackedValue, newValue)) {
return
}
untrackedValue = newValue
trigger()
},
}
}),
}
}

0 comments on commit 20973fb

Please sign in to comment.