Skip to content

Commit e0473d3

Browse files
committed
WIP A bunch of useSelector tests actually pass somehow
1 parent 2210db9 commit e0473d3

File tree

10 files changed

+697
-41
lines changed

10 files changed

+697
-41
lines changed

jest.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ const nextEntryConfig = {
5151
module.exports = {
5252
projects: [
5353
tsStandardConfig,
54-
rnConfig,
55-
standardReact17Config,
56-
nextEntryConfig,
54+
//rnConfig,
55+
//standardReact17Config,
56+
//nextEntryConfig,
5757
],
5858
}

src/components/Context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Context } from 'react'
33
import type { Action, AnyAction, Store } from 'redux'
44
import type { Subscription } from '../utils/Subscription'
55
import type { CheckFrequency } from '../hooks/useSelector'
6+
import type { Node } from '../utils/autotracking/tracking'
67

78
export interface ReactReduxContextValue<
89
SS = any,
@@ -13,6 +14,7 @@ export interface ReactReduxContextValue<
1314
getServerState?: () => SS
1415
stabilityCheck: CheckFrequency
1516
noopCheck: CheckFrequency
17+
trackingNode: Node<Record<string, unknown>>
1618
}
1719

1820
const ContextKey = Symbol.for(`react-redux-context-${ReactVersion}`)

src/components/Provider.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createSubscription } from '../utils/Subscription'
66
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
77
import type { Action, AnyAction, Store } from 'redux'
88
import type { CheckFrequency } from '../hooks/useSelector'
9+
import { createNode, updateNode } from '../utils/autotracking/proxy'
910

1011
export interface ProviderProps<A extends Action = AnyAction, S = unknown> {
1112
/**
@@ -42,14 +43,21 @@ function Provider<A extends Action = AnyAction, S = unknown>({
4243
stabilityCheck = 'once',
4344
noopCheck = 'once',
4445
}: ProviderProps<A, S>) {
45-
const contextValue = useMemo(() => {
46-
const subscription = createSubscription(store)
46+
const contextValue: ReactReduxContextValue = useMemo(() => {
47+
const trackingNode = createNode(store.getState() as any)
48+
//console.log('Created tracking node: ', trackingNode)
49+
const subscription = createSubscription(
50+
store as any,
51+
undefined,
52+
trackingNode
53+
)
4754
return {
48-
store,
55+
store: store as any,
4956
subscription,
5057
getServerState: serverState ? () => serverState : undefined,
5158
stabilityCheck,
5259
noopCheck,
60+
trackingNode,
5361
}
5462
}, [store, serverState, stabilityCheck, noopCheck])
5563

src/hooks/useSelector.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useDebugValue, useRef } from 'react'
1+
import { useCallback, useDebugValue, useMemo, useRef } from 'react'
22

33
import {
44
createReduxContextHook,
@@ -8,6 +8,8 @@ import { ReactReduxContext } from '../components/Context'
88
import type { EqualityFn, NoInfer } from '../types'
99
import type { uSESWS } from '../utils/useSyncExternalStore'
1010
import { notInitialized } from '../utils/useSyncExternalStore'
11+
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
12+
import { createCache } from '../utils/autotracking/autotracking'
1113

1214
export type CheckFrequency = 'never' | 'once' | 'always'
1315

@@ -80,6 +82,7 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
8082
getServerState,
8183
stabilityCheck: globalStabilityCheck,
8284
noopCheck: globalNoopCheck,
85+
trackingNode,
8386
} = useReduxContext()!
8487

8588
const firstRun = useRef(true)
@@ -136,11 +139,44 @@ export function createSelectorHook(context = ReactReduxContext): UseSelector {
136139
[selector, globalStabilityCheck, stabilityCheck]
137140
)
138141

142+
const latestWrappedSelectorRef = useRef(wrappedSelector)
143+
144+
console.log(
145+
'Writing latest selector. Same reference? ',
146+
wrappedSelector === latestWrappedSelectorRef.current
147+
)
148+
latestWrappedSelectorRef.current = wrappedSelector
149+
150+
const cache = useMemo(() => {
151+
const cache = createCache(() => {
152+
console.log('Wrapper cache called: ', store.getState())
153+
return latestWrappedSelectorRef.current(trackingNode.proxy as TState)
154+
})
155+
return cache
156+
}, [trackingNode])
157+
158+
const subscribeToStore = useMemo(() => {
159+
const subscribeToStore = (onStoreChange: () => void) => {
160+
const wrappedOnStoreChange = () => {
161+
// console.log('wrappedOnStoreChange')
162+
return onStoreChange()
163+
}
164+
// console.log('Subscribing to store with tracking')
165+
return subscription.addNestedSub(wrappedOnStoreChange, {
166+
trigger: 'tracked',
167+
cache,
168+
})
169+
}
170+
return subscribeToStore
171+
}, [subscription])
172+
139173
const selectedState = useSyncExternalStoreWithSelector(
140-
subscription.addNestedSub,
174+
//subscription.addNestedSub,
175+
subscribeToStore,
141176
store.getState,
177+
//() => trackingNode.proxy as TState,
142178
getServerState || store.getState,
143-
wrappedSelector,
179+
cache.getValue,
144180
equalityFn
145181
)
146182

src/utils/Subscription.ts

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1+
import type { Store } from 'redux'
12
import { getBatch } from './batch'
3+
import type { Node } from './autotracking/tracking'
4+
5+
import {
6+
createCache,
7+
TrackingCache,
8+
$REVISION,
9+
} from './autotracking/autotracking'
10+
import { updateNode } from './autotracking/proxy'
211

312
// encapsulates the subscription logic for connecting a component to the redux store, as
413
// well as nesting subscriptions of descendant components, so that we can ensure the
@@ -10,6 +19,9 @@ type Listener = {
1019
callback: VoidFunc
1120
next: Listener | null
1221
prev: Listener | null
22+
trigger: 'always' | 'tracked'
23+
selectorCache?: TrackingCache
24+
subscriberCache?: TrackingCache
1325
}
1426

1527
function createListenerCollection() {
@@ -24,10 +36,29 @@ function createListenerCollection() {
2436
},
2537

2638
notify() {
39+
//console.log('Notifying subscribers')
2740
batch(() => {
2841
let listener = first
2942
while (listener) {
30-
listener.callback()
43+
//console.log('Listener: ', listener)
44+
if (listener.trigger == 'tracked') {
45+
if (listener.selectorCache!.needsRecalculation()) {
46+
console.log('Calling subscriber due to recalc need')
47+
// console.log(
48+
// 'Calling subscriber due to recalc. Revision before: ',
49+
// $REVISION
50+
// )
51+
listener.callback()
52+
//console.log('Revision after: ', $REVISION)
53+
} else {
54+
console.log(
55+
'Skipping subscriber, no recalc: ',
56+
listener.selectorCache
57+
)
58+
}
59+
} else {
60+
listener.callback()
61+
}
3162
listener = listener.next
3263
}
3364
})
@@ -43,13 +74,29 @@ function createListenerCollection() {
4374
return listeners
4475
},
4576

46-
subscribe(callback: () => void) {
77+
subscribe(
78+
callback: () => void,
79+
options: AddNestedSubOptions = { trigger: 'always' }
80+
) {
4781
let isSubscribed = true
4882

83+
//console.log('Adding listener: ', options.trigger)
84+
4985
let listener: Listener = (last = {
5086
callback,
5187
next: null,
5288
prev: last,
89+
trigger: options.trigger,
90+
selectorCache:
91+
options.trigger === 'tracked' ? options.cache! : undefined,
92+
// subscriberCache:
93+
// options.trigger === 'tracked'
94+
// ? createCache(() => {
95+
// console.log('Calling subscriberCache')
96+
// listener.selectorCache!.get()
97+
// callback()
98+
// })
99+
// : undefined,
53100
})
54101

55102
if (listener.prev) {
@@ -79,13 +126,18 @@ function createListenerCollection() {
79126

80127
type ListenerCollection = ReturnType<typeof createListenerCollection>
81128

129+
interface AddNestedSubOptions {
130+
trigger: 'always' | 'tracked'
131+
cache?: TrackingCache
132+
}
133+
82134
export interface Subscription {
83-
addNestedSub: (listener: VoidFunc) => VoidFunc
135+
addNestedSub: (listener: VoidFunc, options?: AddNestedSubOptions) => VoidFunc
84136
notifyNestedSubs: VoidFunc
85137
handleChangeWrapper: VoidFunc
86138
isSubscribed: () => boolean
87139
onStateChange?: VoidFunc | null
88-
trySubscribe: VoidFunc
140+
trySubscribe: (options?: AddNestedSubOptions) => void
89141
tryUnsubscribe: VoidFunc
90142
getListeners: () => ListenerCollection
91143
}
@@ -95,16 +147,28 @@ const nullListeners = {
95147
get: () => [],
96148
} as unknown as ListenerCollection
97149

98-
export function createSubscription(store: any, parentSub?: Subscription) {
150+
export function createSubscription(
151+
store: Store,
152+
parentSub?: Subscription,
153+
trackingNode?: Node<any>
154+
) {
99155
let unsubscribe: VoidFunc | undefined
100156
let listeners: ListenerCollection = nullListeners
101157

102-
function addNestedSub(listener: () => void) {
103-
trySubscribe()
104-
return listeners.subscribe(listener)
158+
function addNestedSub(
159+
listener: () => void,
160+
options: AddNestedSubOptions = { trigger: 'always' }
161+
) {
162+
//console.log('addNestedSub: ', options)
163+
trySubscribe(options)
164+
return listeners.subscribe(listener, options)
105165
}
106166

107167
function notifyNestedSubs() {
168+
if (store && trackingNode) {
169+
//console.log('Updating node in notifyNestedSubs')
170+
updateNode(trackingNode, store.getState())
171+
}
108172
listeners.notify()
109173
}
110174

@@ -118,10 +182,11 @@ export function createSubscription(store: any, parentSub?: Subscription) {
118182
return Boolean(unsubscribe)
119183
}
120184

121-
function trySubscribe() {
185+
function trySubscribe(options: AddNestedSubOptions = { trigger: 'always' }) {
122186
if (!unsubscribe) {
187+
//console.log('trySubscribe, parentSub: ', parentSub)
123188
unsubscribe = parentSub
124-
? parentSub.addNestedSub(handleChangeWrapper)
189+
? parentSub.addNestedSub(handleChangeWrapper, options)
125190
: store.subscribe(handleChangeWrapper)
126191

127192
listeners = createListenerCollection()

0 commit comments

Comments
 (0)