Skip to content

Commit

Permalink
fix(query-core): convert notifyManager from factory function to class
Browse files Browse the repository at this point in the history
  • Loading branch information
manudeli committed Aug 21, 2024
1 parent 7ac71ac commit fc5b2de
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 60 deletions.
12 changes: 6 additions & 6 deletions packages/query-core/src/__tests__/notifyManager.test.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { describe, expect, it, vi } from 'vitest'
import { createNotifyManager } from '../notifyManager'
import { NotifyManager } from '../notifyManager'
import { sleep } from './utils'

describe('notifyManager', () => {
it('should use default notifyFn', async () => {
const notifyManagerTest = createNotifyManager()
const notifyManagerTest = new NotifyManager()
const callbackSpy = vi.fn()
notifyManagerTest.schedule(callbackSpy)
await sleep(1)
expect(callbackSpy).toHaveBeenCalled()
})

it('should use default batchNotifyFn', async () => {
const notifyManagerTest = createNotifyManager()
const notifyManagerTest = new NotifyManager()
const callbackScheduleSpy = vi
.fn()
.mockImplementation(async () => await sleep(20))
Expand All @@ -34,7 +34,7 @@ describe('notifyManager', () => {
it('should use a custom scheduler when configured', async () => {
const customCallback = vi.fn((cb) => queueMicrotask(cb))

const notifyManagerTest = createNotifyManager()
const notifyManagerTest = new NotifyManager()
const notifySpy = vi.fn()
notifyManagerTest.setScheduler(customCallback)
notifyManagerTest.setNotifyFunction(notifySpy)
Expand All @@ -50,7 +50,7 @@ describe('notifyManager', () => {
})

it('should notify if error is thrown', async () => {
const notifyManagerTest = createNotifyManager()
const notifyManagerTest = new NotifyManager()
const notifySpy = vi.fn()

notifyManagerTest.setNotifyFunction(notifySpy)
Expand All @@ -69,7 +69,7 @@ describe('notifyManager', () => {
})

it('typeDefs should catch proper signatures', async () => {
const notifyManagerTest = createNotifyManager()
const notifyManagerTest = new NotifyManager()

// we define some fn with its signature:
const fn: (a: string, b: number) => string = (a, b) => a + b
Expand Down
92 changes: 38 additions & 54 deletions packages/query-core/src/notifyManager.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,90 @@
// TYPES

type NotifyCallback = () => void

type NotifyFunction = (callback: () => void) => void

type BatchNotifyFunction = (callback: () => void) => void

type BatchCallsCallback<T extends Array<unknown>> = (...args: T) => void

type ScheduleFunction = (callback: () => void) => void

export function createNotifyManager() {
let queue: Array<NotifyCallback> = []
let transactions = 0
let notifyFn: NotifyFunction = (callback) => {
export class NotifyManager {
#queue: Array<NotifyCallback> = []
#transactions = 0
#notifyFn: NotifyFunction = (callback) => {
callback()
}
let batchNotifyFn: BatchNotifyFunction = (callback: () => void) => {
#batchNotifyFn: BatchNotifyFunction = (callback: () => void) => {
callback()
}
let scheduleFn: ScheduleFunction = (cb) => setTimeout(cb, 0)
#scheduleFn: ScheduleFunction = (callback) => setTimeout(callback, 0)
#flush = () => {
const originalQueue = this.#queue
this.#queue = []
if (originalQueue.length) {
this.#scheduleFn(() => {
this.#batchNotifyFn(() => {
originalQueue.forEach((callback) => {
this.#notifyFn(callback)
})
})
})
}
}

const setScheduler = (fn: ScheduleFunction) => {
scheduleFn = fn
setScheduler = (fn: ScheduleFunction) => {
this.#scheduleFn = fn
}

const batch = <T>(callback: () => T): T => {
batch = <T>(callback: () => T): T => {
let result
transactions++
this.#transactions++
try {
result = callback()
} finally {
transactions--
if (!transactions) {
flush()
this.#transactions--
if (!this.#transactions) {
this.#flush()
}
}
return result
}

const schedule = (callback: NotifyCallback): void => {
if (transactions) {
queue.push(callback)
schedule = (callback: NotifyCallback): void => {
if (this.#transactions) {
this.#queue.push(callback)
} else {
scheduleFn(() => {
notifyFn(callback)
this.#scheduleFn(() => {
this.#notifyFn(callback)
})
}
}

/**
* All calls to the wrapped function will be batched.
*/
const batchCalls = <T extends Array<unknown>>(
batchCalls = <T extends Array<unknown>>(
callback: BatchCallsCallback<T>,
): BatchCallsCallback<T> => {
return (...args) => {
schedule(() => {
this.schedule(() => {
callback(...args)
})
}
}

const flush = (): void => {
const originalQueue = queue
queue = []
if (originalQueue.length) {
scheduleFn(() => {
batchNotifyFn(() => {
originalQueue.forEach((callback) => {
notifyFn(callback)
})
})
})
}
}

/**
* Use this method to set a custom notify function.
* This can be used to for example wrap notifications with `React.act` while running tests.
*/
const setNotifyFunction = (fn: NotifyFunction) => {
notifyFn = fn
setNotifyFunction = (fn: NotifyFunction) => {
this.#notifyFn = fn
}

/**
* Use this method to set a custom function to batch notifications together into a single tick.
* By default React Query will use the batch function provided by ReactDOM or React Native.
*/
const setBatchNotifyFunction = (fn: BatchNotifyFunction) => {
batchNotifyFn = fn
setBatchNotifyFunction = (fn: BatchNotifyFunction) => {
this.#batchNotifyFn = fn
}

return {
batch,
batchCalls,
schedule,
setNotifyFunction,
setBatchNotifyFunction,
setScheduler,
} as const
}

// SINGLETON
export const notifyManager = createNotifyManager()
export const notifyManager = new NotifyManager()

0 comments on commit fc5b2de

Please sign in to comment.