Skip to content

Commit 9b02a44

Browse files
feat: support views
1 parent c5b93ef commit 9b02a44

File tree

4 files changed

+39
-11
lines changed

4 files changed

+39
-11
lines changed

app/routes/hook.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,20 @@ export const meta: MetaFunction = () => {
1212
export default function HookPage() {
1313
const {
1414
count,
15-
submodel: { title },
15+
submodel: { title, allCaps, lowercase },
1616
} = useObservable(store);
1717

18+
console.log("Rendering")
19+
1820
return (
1921
<div className="App">
2022
<h1>This page uses a hook for observing properties</h1>
2123
<p>Open up React DevTools and you'll see that the `HookPage` component is properly memoized with React Compiler</p>
2224
<p>
2325
{count}: {title}
2426
</p>
27+
<p>This is a computed view that capitalizes the title: {allCaps}</p>
28+
<p>This is a lazily evaluated view that converts the title to lowercase: {lowercase()}</p>
2529
<div className="flex flex-wrap gap-2">
2630
<button
2731
onClick={() => store.increment()}

app/routes/observer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const meta: MetaFunction = () => {
1212
export default observer(function ObserverPage() {
1313
const {
1414
count,
15-
submodel: { title },
15+
submodel: { title, allCaps, lowercase },
1616
} = store;
1717

1818
return (
@@ -22,6 +22,8 @@ export default observer(function ObserverPage() {
2222
<p>
2323
{count}: {title}
2424
</p>
25+
<p>This is a computed view that capitalizes the title: {allCaps}</p>
26+
<p>This is a lazily evaluated view that converts the title to lowercase: {lowercase()}</p>
2527
<div className="flex flex-wrap gap-2">
2628
<button
2729
onClick={() => store.increment()}

hooks/useObservable.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
11
import { useEffect, useState } from "react";
2-
import { IStateTreeNode, getSnapshot, onSnapshot } from "mobx-state-tree";
2+
import { IStateTreeNode, t, getSnapshot, onSnapshot } from "mobx-state-tree";
3+
4+
// Helper to check if a value is an MST node
5+
function isMSTNode(value: any): value is IStateTreeNode {
6+
return value && typeof value === "object" && "toJSON" in value;
7+
}
38

49
export function useObservableProperty<
510
T extends IStateTreeNode,
611
K extends keyof ReturnType<typeof getSnapshot<T>>
712
>(model: T, property: K) {
813
const [value, setValue] = useState(() => {
9-
const snapshot = getSnapshot(model);
10-
return snapshot[property];
14+
return model[property];
1115
});
1216

1317
useEffect(() => {
1418
if (!model) return;
15-
1619
const disposer = onSnapshot(model, (snapshot) => {
17-
if (snapshot[property] !== value) {
18-
setValue(snapshot[property]);
20+
if (model[property] !== value) {
21+
setValue(model[property]);
1922
}
2023
});
21-
2224
return disposer;
2325
}, [model, property, value]);
2426

27+
// If the value is itself an MST node, return a proxy for it
28+
const propertyValue = model[property];
29+
if (isMSTNode(propertyValue)) {
30+
return useObservable(propertyValue);
31+
}
32+
33+
// If it's a function, return the function bound to the model
34+
if (typeof propertyValue === 'function') {
35+
return propertyValue.bind(model);
36+
}
37+
2538
return value;
2639
}
2740

@@ -37,4 +50,4 @@ export function useObservable<T extends IStateTreeNode>(model: T) {
3750
return undefined;
3851
},
3952
});
40-
}
53+
}

store/store.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ const SubModel = t
66
})
77
.actions((sub) => ({
88
randomize() {
9+
console.log("Randomizing title - should render");
910
const randomString = Array.from({ length: 8 }, () =>
1011
"Hello World".charAt(Math.random() * 10)
1112
).join("");
1213
sub.title = randomString;
1314
},
14-
}));
15+
}))
16+
.views((self) => ({
17+
get allCaps() {
18+
return self.title.toUpperCase();
19+
},
20+
lowercase() {
21+
return self.title.toLowerCase();
22+
}
23+
}))
1524

1625
const SomeModel = t
1726
.model("SomeModel", {

0 commit comments

Comments
 (0)