From b44ad4543bd56cd84929c18b5de345e7d7364f96 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Sat, 26 Jul 2025 18:28:39 +0530 Subject: [PATCH] Create a base vm class: - Implements callback aggregation for subscribe method - Provides `emit` method for re-rendering the UI --- src/viewmodels/base/BaseViewModel.ts | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/viewmodels/base/BaseViewModel.ts diff --git a/src/viewmodels/base/BaseViewModel.ts b/src/viewmodels/base/BaseViewModel.ts new file mode 100644 index 0000000000..f08edb8681 --- /dev/null +++ b/src/viewmodels/base/BaseViewModel.ts @@ -0,0 +1,48 @@ +/* +Copyright 2025 New Vector Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import type { ViewModelNew } from "../../shared-components/ViewModel"; + +export abstract class BaseViewModel

implements ViewModelNew { + /** + * We are relying on {@link https://react.dev/reference/react/useSyncExternalStore|useSyncExternalStore} + * to keep the react component in sync with view model. + * We only use this snapshot as way of convincing react that it should do a re-render when {@link emit} is called. + */ + private snapshot: unknown = {}; + private callbacks: Set<() => void> = new Set(); + + public constructor(protected props: P) {} + + public getSnapshot(): unknown { + return this.snapshot; + } + + public subscribe(callback: () => void): () => void { + this.callbacks.add(callback); + return () => { + this.callbacks.delete(callback); + }; + } + + /** + * Re-render any subscribed components + */ + protected emit(): void { + /** + * When we invoke the callbacks, react will check if the result of getSnapshot() + * matches the previously known snapshot value via Object.is(). + * Since the intention of calling this method is to make the UI re-render, we want + * that comparison to fail. + * We can do that by assigning a new empty object to snapshot. + */ + this.snapshot = {}; + for (const callback of this.callbacks) { + callback(); + } + } +}