diff --git a/src/hooks/useSelector.ts b/src/hooks/useSelector.ts index 4241edd92..a31c9403f 100644 --- a/src/hooks/useSelector.ts +++ b/src/hooks/useSelector.ts @@ -170,7 +170,7 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector { // console.log('wrappedOnStoreChange') return onStoreChange() } - // console.log('Subscribing to store with tracking') + console.log('Subscribing to store with tracking') return subscription.addNestedSub(wrappedOnStoreChange, { trigger: 'tracked', cache: cacheWrapper.current, diff --git a/src/utils/Subscription.ts b/src/utils/Subscription.ts index c03f90884..9e27e0c14 100644 --- a/src/utils/Subscription.ts +++ b/src/utils/Subscription.ts @@ -39,25 +39,25 @@ function createListenerCollection() { }, notify() { - //console.log('Notifying subscribers') + console.log('Notifying subscribers') batch(() => { let listener = first while (listener) { - //console.log('Listener: ', listener) + console.log('Listener: ', listener) if (listener.trigger == 'tracked') { if (listener.selectorCache!.cache.needsRecalculation()) { - //console.log('Calling subscriber due to recalc need') - // console.log( - // 'Calling subscriber due to recalc. Revision before: ', - // $REVISION - // ) + console.log('Calling subscriber due to recalc need') + console.log( + 'Calling subscriber due to recalc. Revision before: ', + $REVISION + ) listener.callback() - //console.log('Revision after: ', $REVISION) + console.log('Revision after: ', $REVISION) } else { - // console.log( - // 'Skipping subscriber, no recalc: ', - // listener.selectorCache - // ) + console.log( + 'Skipping subscriber, no recalc: ', + listener.selectorCache + ) } } else { listener.callback() @@ -83,7 +83,7 @@ function createListenerCollection() { ) { let isSubscribed = true - //console.log('Adding listener: ', options.trigger) + console.log('Adding listener: ', options.trigger) let listener: Listener = (last = { callback, @@ -162,14 +162,14 @@ export function createSubscription( listener: () => void, options: AddNestedSubOptions = { trigger: 'always' } ) { - //console.log('addNestedSub: ', options) + console.log('addNestedSub: ', options) trySubscribe(options) return listeners.subscribe(listener, options) } function notifyNestedSubs() { if (store && trackingNode) { - //console.log('Updating node in notifyNestedSubs') + console.log('Updating node in notifyNestedSubs') updateNode(trackingNode, store.getState()) } listeners.notify() @@ -187,7 +187,7 @@ export function createSubscription( function trySubscribe(options: AddNestedSubOptions = { trigger: 'always' }) { if (!unsubscribe) { - //console.log('trySubscribe, parentSub: ', parentSub) + console.log('trySubscribe, parentSub: ', parentSub) unsubscribe = parentSub ? parentSub.addNestedSub(handleChangeWrapper, options) : store.subscribe(handleChangeWrapper) diff --git a/src/utils/autotracking/autotracking.ts b/src/utils/autotracking/autotracking.ts index eca33536a..79e9a5c04 100644 --- a/src/utils/autotracking/autotracking.ts +++ b/src/utils/autotracking/autotracking.ts @@ -87,14 +87,15 @@ export class TrackingCache { needsRecalculation() { if (!this._needsRecalculation) { - this._needsRecalculation = this.revision > this._cachedRevision + this._needsRecalculation = + this.revision > this._cachedRevision || this._cachedRevision === -1 } - // console.log( - // 'Needs recalculation: ', - // this._needsRecalculation, - // this._cachedRevision, - // this._cachedValue - // ) + console.log( + 'Needs recalculation: ', + this._needsRecalculation, + this._cachedRevision, + this._cachedValue + ) return this._needsRecalculation } diff --git a/test/hooks/useSelector.spec.tsx b/test/hooks/useSelector.spec.tsx index 2dae0b41d..b02728182 100644 --- a/test/hooks/useSelector.spec.tsx +++ b/test/hooks/useSelector.spec.tsx @@ -26,7 +26,7 @@ import type { } from '../../src/index' import type { FunctionComponent, DispatchWithoutAction, ReactNode } from 'react' import type { Store, AnyAction, Action } from 'redux' -import { createSlice, configureStore } from '@reduxjs/toolkit' +import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit' import type { UseSelectorOptions } from '../../src/hooks/useSelector' // disable checks by default @@ -1051,6 +1051,129 @@ describe('React', () => { }) }) }) + + describe('Auto-tracking behavior checks', () => { + interface Todo { + id: number + name: string + completed: boolean + } + + type TodosState = Todo[] + + const counterSlice = createSlice({ + name: 'counters', + initialState: { + deeply: { + nested: { + really: { + deeply: { + nested: { + c1: { value: 0 }, + }, + }, + }, + }, + }, + + c2: { value: 0 }, + }, + reducers: { + increment1(state) { + // state.c1.value++ + state.deeply.nested.really.deeply.nested.c1.value++ + }, + increment2(state) { + state.c2.value++ + }, + }, + }) + + const todosSlice = createSlice({ + name: 'todos', + initialState: [ + { id: 0, name: 'a', completed: false }, + { id: 1, name: 'b', completed: false }, + { id: 2, name: 'c', completed: false }, + ] as TodosState, + reducers: { + toggleCompleted(state, action: PayloadAction) { + const todo = state.find((todo) => todo.id === action.payload) + if (todo) { + todo.completed = !todo.completed + } + }, + setName(state) { + state[1].name = 'd' + }, + }, + }) + + function makeStore() { + return configureStore({ + reducer: { + counter: counterSlice.reducer, + todos: todosSlice.reducer, + }, + middleware: (gDM) => + gDM({ + serializableCheck: false, + immutableCheck: false, + }), + }) + } + + type AppStore = ReturnType + let store: AppStore + type RootState = ReturnType + + const useAppSelector: TypedUseSelectorHook = useSelector + + beforeEach(() => { + store = makeStore() + }) + + test.only('should correctly handle updates to nested data', async () => { + let itemSelectorCallsCount = 0 + let listSelectorCallsCount = 0 + function TodoListItem({ todoId }: { todoId: number }) { + console.log('TodoListItem render: ', todoId) + const todo = useAppSelector((state) => { + itemSelectorCallsCount++ + return state.todos.find((t) => t.id === todoId) + })! + return ( +
+ {todo.id}: {todo.name} ({todo.completed}) +
+ ) + } + + function TodoList() { + const todoIds = useAppSelector((state) => { + listSelectorCallsCount++ + return state.todos.map((t) => t.id) + }) + console.log('TodoList render: ', todoIds) + return ( + <> + {todoIds.map((id) => ( + + ))} + + ) + } + + rtl.render( + + + + ) + + expect(listSelectorCallsCount).toBe(1) + expect(itemSelectorCallsCount).toBe(3) + }) + }) }) describe('createSelectorHook', () => {