From 5f8a45dffdf845f28010ecfc6998a16fb34261ce Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Fri, 25 Oct 2024 12:15:43 +0200 Subject: [PATCH 1/7] fix: change subscribeWithSelector API declaration --- src/store.ts | 14 ++++++++++++-- src/thread.ts | 11 +++++++---- src/thread_manager.ts | 10 +++++----- src/types.ts | 2 +- test/unit/threads.test.ts | 9 ++++++--- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/store.ts b/src/store.ts index f76c8bacb..a9a75c0ba 100644 --- a/src/store.ts +++ b/src/store.ts @@ -36,13 +36,23 @@ export class StateStore> { }; }; - public subscribeWithSelector = (selector: (nextValue: T) => O, handler: Handler) => { + public subscribeWithSelector = > & { length?: never }>( + selector: (nextValue: T) => O, + handler: Handler, + ) => { // begin with undefined to reduce amount of selector calls let selectedValues: O | undefined; const wrappedHandler: Handler = (nextValue) => { const newlySelectedValues = selector(nextValue); - const hasUpdatedValues = selectedValues?.some((value, index) => value !== newlySelectedValues[index]) ?? true; + + let hasUpdatedValues = !selectedValues; + + for (const key in selectedValues) { + if (selectedValues[key] === newlySelectedValues[key]) continue; + hasUpdatedValues = true; + break; + } if (!hasUpdatedValues) return; diff --git a/src/thread.ts b/src/thread.ts index 9ea26141f..13874da67 100644 --- a/src/thread.ts +++ b/src/thread.ts @@ -197,8 +197,11 @@ export class Thread { private subscribeMarkActiveThreadRead = () => { return this.state.subscribeWithSelector( - (nextValue) => [nextValue.active, ownUnreadCountSelector(this.client.userID)(nextValue)], - ([active, unreadMessageCount]) => { + (nextValue) => ({ + active: nextValue.active, + unreadMessageCount: ownUnreadCountSelector(this.client.userID)(nextValue), + }), + ({ active, unreadMessageCount }) => { if (!active || !unreadMessageCount) return; this.throttledMarkAsRead(); }, @@ -207,8 +210,8 @@ export class Thread { private subscribeReloadActiveStaleThread = () => this.state.subscribeWithSelector( - (nextValue) => [nextValue.active, nextValue.isStateStale], - ([active, isStateStale]) => { + (nextValue) => ({ active: nextValue.active, isStateStale: nextValue.isStateStale }), + ({ active, isStateStale }) => { if (active && isStateStale) { this.reload(); } diff --git a/src/thread_manager.ts b/src/thread_manager.ts index 3d8d47976..3808676f1 100644 --- a/src/thread_manager.ts +++ b/src/thread_manager.ts @@ -119,9 +119,9 @@ export class ThreadManager { private subscribeManageThreadSubscriptions = () => this.state.subscribeWithSelector( - (nextValue) => [nextValue.threads] as const, - ([nextThreads], prev) => { - const [prevThreads = []] = prev ?? []; + (nextValue) => ({ threads: nextValue.threads } as const), + ({ threads: nextThreads }, prev) => { + const { threads: prevThreads = [] } = prev ?? {}; // Thread instance was removed if there's no thread with the given id at all, // or it was replaced with a new instance const removedThreads = prevThreads.filter((thread) => thread !== this.threadsById[thread.id]); @@ -133,8 +133,8 @@ export class ThreadManager { private subscribeReloadOnActivation = () => this.state.subscribeWithSelector( - (nextValue) => [nextValue.active], - ([active]) => { + (nextValue) => ({ active: nextValue.active }), + ({ active }) => { if (active) this.reload(); }, ); diff --git a/src/types.ts b/src/types.ts index 99fd747ef..c59215442 100644 --- a/src/types.ts +++ b/src/types.ts @@ -963,10 +963,10 @@ export type CreateChannelOptions = { diff --git a/test/unit/threads.test.ts b/test/unit/threads.test.ts index 4b70be557..36473a2c1 100644 --- a/test/unit/threads.test.ts +++ b/test/unit/threads.test.ts @@ -1322,14 +1322,17 @@ describe('Threads 2.0', () => { }, })); const spy = sinon.spy(); - threadManager.state.subscribeWithSelector((nextValue) => [nextValue.pagination.isLoadingNext], spy); + threadManager.state.subscribeWithSelector( + (nextValue) => ({ isLoadingNext: nextValue.pagination.isLoadingNext }), + spy, + ); spy.resetHistory(); await threadManager.loadNextPage(); expect(spy.callCount).to.equal(2); - expect(spy.firstCall.calledWith([true])).to.be.true; - expect(spy.lastCall.calledWith([false])).to.be.true; + expect(spy.firstCall.calledWith({ isLoadingNext: true })).to.be.true; + expect(spy.lastCall.calledWith({ isLoadingNext: false })).to.be.true; }); it('updates thread list and pagination', async () => { From 54100554fda062e8cec4b83ba5d405ed0fb9b952 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Fri, 25 Oct 2024 13:01:08 +0200 Subject: [PATCH 2/7] chore: add deprecation warning --- src/store.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/store.ts b/src/store.ts index a9a75c0ba..30fc2eb85 100644 --- a/src/store.ts +++ b/src/store.ts @@ -48,6 +48,12 @@ export class StateStore> { let hasUpdatedValues = !selectedValues; + if (Array.isArray(selectedValues)) { + console.warn( + 'The API of our state store has changed. Instead of returning an array in the selector, please return a named object of properties.', + ); + } + for (const key in selectedValues) { if (selectedValues[key] === newlySelectedValues[key]) continue; hasUpdatedValues = true; From cc8b38a77967362356b16957f984fa76eb8aca04 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Fri, 25 Oct 2024 14:22:20 +0200 Subject: [PATCH 3/7] fix: remove utility type and add additional check to warning --- src/store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/store.ts b/src/store.ts index 30fc2eb85..5d615a578 100644 --- a/src/store.ts +++ b/src/store.ts @@ -36,7 +36,7 @@ export class StateStore> { }; }; - public subscribeWithSelector = > & { length?: never }>( + public subscribeWithSelector = >>( selector: (nextValue: T) => O, handler: Handler, ) => { @@ -48,7 +48,7 @@ export class StateStore> { let hasUpdatedValues = !selectedValues; - if (Array.isArray(selectedValues)) { + if (selectedValues && Array.isArray(selectedValues)) { console.warn( 'The API of our state store has changed. Instead of returning an array in the selector, please return a named object of properties.', ); From f6aa1800f993d305450d721cf51426e4006d476b Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Fri, 25 Oct 2024 16:03:17 +0200 Subject: [PATCH 4/7] Minor adjustments --- src/store.ts | 4 ++-- src/thread_manager.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/store.ts b/src/store.ts index 5d615a578..8c39c001e 100644 --- a/src/store.ts +++ b/src/store.ts @@ -48,9 +48,9 @@ export class StateStore> { let hasUpdatedValues = !selectedValues; - if (selectedValues && Array.isArray(selectedValues)) { + if (Array.isArray(newlySelectedValues)) { console.warn( - 'The API of our state store has changed. Instead of returning an array in the selector, please return a named object of properties.', + '[StreamChat]: The API of our StateStore has changed. Instead of returning an array in the selector, please return a named object of properties.', ); } diff --git a/src/thread_manager.ts b/src/thread_manager.ts index 3808676f1..c06f1618a 100644 --- a/src/thread_manager.ts +++ b/src/thread_manager.ts @@ -119,7 +119,7 @@ export class ThreadManager { private subscribeManageThreadSubscriptions = () => this.state.subscribeWithSelector( - (nextValue) => ({ threads: nextValue.threads } as const), + (nextValue) => ({ threads: nextValue.threads }), ({ threads: nextThreads }, prev) => { const { threads: prevThreads = [] } = prev ?? {}; // Thread instance was removed if there's no thread with the given id at all, From 06d3862e79930b77e788780c705b0f13abbf421a Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Mon, 28 Oct 2024 14:36:26 +0100 Subject: [PATCH 5/7] Add back array type --- src/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store.ts b/src/store.ts index 8c39c001e..e3bdde705 100644 --- a/src/store.ts +++ b/src/store.ts @@ -36,7 +36,7 @@ export class StateStore> { }; }; - public subscribeWithSelector = >>( + public subscribeWithSelector = > | Readonly>( selector: (nextValue: T) => O, handler: Handler, ) => { From e62eaa0bfe8a4877137978c5327c9634bf5b7607 Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Mon, 28 Oct 2024 14:43:55 +0100 Subject: [PATCH 6/7] Ignore check --- src/store.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/store.ts b/src/store.ts index e3bdde705..0e1f61d97 100644 --- a/src/store.ts +++ b/src/store.ts @@ -55,6 +55,7 @@ export class StateStore> { } for (const key in selectedValues) { + // @ts-ignore TODO: remove array support (Readonly) if (selectedValues[key] === newlySelectedValues[key]) continue; hasUpdatedValues = true; break; From ecd268dccc5e353c9f5b7d3f6aaedb1f21a34a4f Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Mon, 28 Oct 2024 15:20:19 +0100 Subject: [PATCH 7/7] Add static logCount to StateStore --- src/store.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/store.ts b/src/store.ts index 0e1f61d97..e717b0a2e 100644 --- a/src/store.ts +++ b/src/store.ts @@ -9,6 +9,8 @@ function isPatch(value: T | Patch): value is Patch { export class StateStore> { private handlerSet = new Set>(); + private static logCount = 5; + constructor(private value: T) {} public next = (newValueOrPatch: T | Patch): void => { @@ -48,10 +50,11 @@ export class StateStore> { let hasUpdatedValues = !selectedValues; - if (Array.isArray(newlySelectedValues)) { + if (Array.isArray(newlySelectedValues) && StateStore.logCount > 0) { console.warn( '[StreamChat]: The API of our StateStore has changed. Instead of returning an array in the selector, please return a named object of properties.', ); + StateStore.logCount--; } for (const key in selectedValues) {