Skip to content

Commit dab2c11

Browse files
committed
Create hook to auto dispose vm
1 parent 42f8247 commit dab2c11

File tree

1 file changed

+61
-0
lines changed

1 file changed

+61
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2025 Element Creations Ltd.
3+
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
4+
Please see LICENSE files in the repository root for full details.
5+
*/
6+
7+
import { useEffect, useState } from "react";
8+
import type { BaseViewModel } from "./BaseViewModel";
9+
10+
type VmCreator<B extends BaseViewModel<unknown, unknown>> = () => B;
11+
12+
/**
13+
* Instantiate a view-model that gets disposed when the calling react component unmounts.
14+
* In other words, this hook ties the lifecycle of a view-model to the lifecycle of a
15+
* react component.
16+
*
17+
* @param vmCreator A function that returns a view-model instance
18+
* @returns view-model instance from vmCreator
19+
* @example
20+
* const vm = useCreateAutoDisposedViewModel(() => new FooViewModel({prop1, prop2, ...});
21+
*/
22+
export function useCreateAutoDisposedViewModel<B extends BaseViewModel<unknown, unknown>>(vmCreator: VmCreator<B>): B {
23+
/**
24+
* The view-model instance may be replaced by a different instance in some scenarios.
25+
* We want to be sure that whatever react component called this hook gets re-rendered
26+
* when this happens, hence the state.
27+
*/
28+
const [viewModel, setViewModel] = useState<B>(vmCreator);
29+
30+
/**
31+
* Our intention here is to ensure that the dispose method of the view-model gets called
32+
* when the component that uses this hook unmounts.
33+
* We can do that by combining a useEffect cleanup with an empty dependency array.
34+
*/
35+
useEffect(() => {
36+
let toDispose = viewModel;
37+
38+
/**
39+
* Because we use react strict mode, react will run our effects twice in dev mode to make
40+
* sure that they are pure.
41+
* This presents a complication - the vm instance that we created in our state initializer
42+
* will get disposed on the first cleanup.
43+
* So we'll recreate the view-model if it's already disposed.
44+
*/
45+
if (viewModel.isDisposed) {
46+
const newViewModel = vmCreator();
47+
// Change toDispose so that we don't end up disposing the already disposed vm.
48+
toDispose = newViewModel;
49+
setViewModel(newViewModel);
50+
}
51+
return () => {
52+
// Dispose the view-model when this component unmounts
53+
toDispose.dispose();
54+
};
55+
56+
// eslint-disable-next-line react-compiler/react-compiler
57+
// eslint-disable-next-line react-hooks/exhaustive-deps
58+
}, []);
59+
60+
return viewModel;
61+
}

0 commit comments

Comments
 (0)