mirror of
https://github.com/element-hq/element-web.git
synced 2025-12-11 01:40:42 +00:00
* Create hook to auto dispose vm * Write test * Fix lint error * Use proper license * whatever -> whichever * Remove eslint comments * Add clarifying comment
69 lines
2.8 KiB
TypeScript
69 lines
2.8 KiB
TypeScript
/*
|
|
Copyright 2025 Element Creations 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 { useEffect, useState } from "react";
|
|
|
|
import type { BaseViewModel } from "./BaseViewModel";
|
|
|
|
type VmCreator<B extends BaseViewModel<unknown, unknown>> = () => B;
|
|
|
|
/**
|
|
* Instantiate a view-model that gets disposed when the calling react component unmounts.
|
|
* In other words, this hook ties the lifecycle of a view-model to the lifecycle of a
|
|
* react component.
|
|
*
|
|
* @param vmCreator A function that returns a view-model instance
|
|
* @returns view-model instance from vmCreator
|
|
* @example
|
|
* const vm = useCreateAutoDisposedViewModel(() => new FooViewModel({prop1, prop2, ...});
|
|
*/
|
|
export function useCreateAutoDisposedViewModel<B extends BaseViewModel<unknown, unknown>>(vmCreator: VmCreator<B>): B {
|
|
/**
|
|
* The view-model instance may be replaced by a different instance in some scenarios.
|
|
* We want to be sure that whichever react component called this hook gets re-rendered
|
|
* when this happens, hence the state.
|
|
*/
|
|
const [viewModel, setViewModel] = useState<B>(vmCreator);
|
|
|
|
/**
|
|
* Our intention here is to ensure that the dispose method of the view-model gets called
|
|
* when the component that uses this hook unmounts.
|
|
* We can do that by combining a useEffect cleanup with an empty dependency array.
|
|
*/
|
|
useEffect(() => {
|
|
let toDispose = viewModel;
|
|
|
|
/**
|
|
* Because we use react strict mode, react will run our effects twice in dev mode to make
|
|
* sure that they are pure.
|
|
* This presents a complication - the vm instance that we created in our state initializer
|
|
* will get disposed on the first cleanup.
|
|
* So we'll recreate the view-model if it's already disposed.
|
|
*/
|
|
if (viewModel.isDisposed) {
|
|
const newViewModel = vmCreator();
|
|
// Change toDispose so that we don't end up disposing the already disposed vm.
|
|
toDispose = newViewModel;
|
|
setViewModel(newViewModel);
|
|
}
|
|
return () => {
|
|
// Dispose the view-model when this component unmounts
|
|
toDispose.dispose();
|
|
};
|
|
|
|
/**
|
|
* We explicitly provide an empty dependency array as we don't expect the viewModel/viewCreator to
|
|
* change.
|
|
* Or to put it in another way, the only reason to use this hook is to create/dispose the view-model
|
|
* and that is something that should only happen at the start/end of the lifecycle of this component.
|
|
*/
|
|
// eslint-disable-next-line react-compiler/react-compiler
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
return viewModel;
|
|
}
|