Skip to content

Commit 6259372

Browse files
committed
move useEntityPrivate to separate function
1 parent 55b7821 commit 6259372

File tree

2 files changed

+107
-69
lines changed

2 files changed

+107
-69
lines changed

packages/hypergraph-react/src/HypergraphSpaceContext.tsx

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
useSyncExternalStore,
1515
} from 'react';
1616
import { useHypergraphApp } from './HypergraphAppContext.js';
17+
import { useEntityPrivate } from './internal/use-entity-private.js';
1718
import { useEntityPublic } from './internal/use-entity-public.js';
1819
import { usePublicSpace } from './internal/use-public-space.js';
1920

@@ -182,75 +183,6 @@ export function useQueryLocal<const S extends Entity.AnyNoContext>(type: S, para
182183
return { entities, deletedEntities };
183184
}
184185

185-
function useEntityPrivate<const S extends Entity.AnyNoContext>(
186-
type: S,
187-
params: {
188-
id: string | Id;
189-
enabled?: boolean;
190-
space?: string;
191-
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
192-
},
193-
) {
194-
const { space: spaceFromContext } = useHypergraphSpaceInternal();
195-
const { space: spaceFromParams, include, id, enabled = true } = params;
196-
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: spaceFromParams ?? spaceFromContext, enabled });
197-
const prevEntityRef = useRef<{
198-
data: Entity.Entity<S> | undefined;
199-
invalidEntity: Record<string, string | boolean | number | Date> | undefined;
200-
isPending: boolean;
201-
isError: boolean;
202-
}>({ data: undefined, invalidEntity: undefined, isPending: false, isError: false });
203-
const equals = Schema.equivalence(type);
204-
205-
const subscribe = (callback: () => void) => {
206-
if (!handle || !enabled) {
207-
return () => {};
208-
}
209-
const handleChange = () => {
210-
callback();
211-
};
212-
213-
const handleDelete = () => {
214-
callback();
215-
};
216-
217-
handle.on('change', handleChange);
218-
handle.on('delete', handleDelete);
219-
220-
return () => {
221-
handle.off('change', handleChange);
222-
handle.off('delete', handleDelete);
223-
};
224-
};
225-
226-
return useSyncExternalStore(subscribe, () => {
227-
if (!handle || !enabled) {
228-
return prevEntityRef.current;
229-
}
230-
const doc = handle.doc();
231-
if (doc === undefined) {
232-
return prevEntityRef.current;
233-
}
234-
235-
const found = Entity.findOne(handle, type, include)(id);
236-
if (found === undefined && prevEntityRef.current.data !== undefined) {
237-
// entity was maybe deleted, delete from the ref
238-
prevEntityRef.current = { data: undefined, invalidEntity: undefined, isPending: false, isError: false };
239-
} else if (found !== undefined && prevEntityRef.current.data === undefined) {
240-
prevEntityRef.current = { data: found, invalidEntity: undefined, isPending: false, isError: false };
241-
} else if (
242-
found !== undefined &&
243-
prevEntityRef.current.data !== undefined &&
244-
!equals(found, prevEntityRef.current.data)
245-
) {
246-
// found and ref have a value, compare for equality, if they are not equal, update the ref and return
247-
prevEntityRef.current = { data: found, invalidEntity: undefined, isPending: false, isError: false };
248-
}
249-
250-
return prevEntityRef.current;
251-
});
252-
}
253-
254186
export function useEntity<const S extends Entity.AnyNoContext>(
255187
type: S,
256188
params: {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use client';
2+
3+
import { Entity, type Id, store } from '@graphprotocol/hypergraph';
4+
import { useSelector } from '@xstate/store/react';
5+
import * as Schema from 'effect/Schema';
6+
import { useEffect, useRef, useSyncExternalStore } from 'react';
7+
import { useHypergraphApp } from '../HypergraphAppContext.js';
8+
import { useHypergraphSpaceInternal } from '../HypergraphSpaceContext.js';
9+
10+
const subscribeToSpaceCache = new Map<string, boolean>();
11+
12+
function useSubscribeToSpaceAndGetHandle({ spaceId, enabled }: { spaceId: string; enabled: boolean }) {
13+
const handle = useSelector(store, (state) => {
14+
const space = state.context.spaces.find((space) => space.id === spaceId);
15+
if (!space) {
16+
return undefined;
17+
}
18+
return space.automergeDocHandle;
19+
});
20+
21+
const { subscribeToSpace, isConnecting } = useHypergraphApp();
22+
useEffect(() => {
23+
if (!isConnecting && enabled) {
24+
if (subscribeToSpaceCache.has(spaceId)) {
25+
return;
26+
}
27+
subscribeToSpaceCache.set(spaceId, true);
28+
subscribeToSpace({ spaceId });
29+
}
30+
return () => {
31+
// TODO: unsubscribe from space in case the space ID changes
32+
subscribeToSpaceCache.delete(spaceId);
33+
};
34+
}, [isConnecting, subscribeToSpace, spaceId, enabled]);
35+
36+
return handle;
37+
}
38+
39+
export function useEntityPrivate<const S extends Entity.AnyNoContext>(
40+
type: S,
41+
params: {
42+
id: string | Id;
43+
enabled?: boolean;
44+
space?: string;
45+
include?: { [K in keyof Schema.Schema.Type<S>]?: Record<string, Record<string, never>> } | undefined;
46+
},
47+
) {
48+
const { space: spaceFromContext } = useHypergraphSpaceInternal();
49+
const { space: spaceFromParams, include, id, enabled = true } = params;
50+
const handle = useSubscribeToSpaceAndGetHandle({ spaceId: spaceFromParams ?? spaceFromContext, enabled });
51+
const prevEntityRef = useRef<{
52+
data: Entity.Entity<S> | undefined;
53+
invalidEntity: Record<string, string | boolean | number | Date> | undefined;
54+
isPending: boolean;
55+
isError: boolean;
56+
}>({ data: undefined, invalidEntity: undefined, isPending: false, isError: false });
57+
const equals = Schema.equivalence(type);
58+
59+
const subscribe = (callback: () => void) => {
60+
if (!handle || !enabled) {
61+
return () => {};
62+
}
63+
const handleChange = () => {
64+
callback();
65+
};
66+
67+
const handleDelete = () => {
68+
callback();
69+
};
70+
71+
handle.on('change', handleChange);
72+
handle.on('delete', handleDelete);
73+
74+
return () => {
75+
handle.off('change', handleChange);
76+
handle.off('delete', handleDelete);
77+
};
78+
};
79+
80+
return useSyncExternalStore(subscribe, () => {
81+
if (!handle || !enabled) {
82+
return prevEntityRef.current;
83+
}
84+
const doc = handle.doc();
85+
if (doc === undefined) {
86+
return prevEntityRef.current;
87+
}
88+
89+
const found = Entity.findOne(handle, type, include)(id);
90+
if (found === undefined && prevEntityRef.current.data !== undefined) {
91+
// entity was maybe deleted, delete from the ref
92+
prevEntityRef.current = { data: undefined, invalidEntity: undefined, isPending: false, isError: false };
93+
} else if (found !== undefined && prevEntityRef.current.data === undefined) {
94+
prevEntityRef.current = { data: found, invalidEntity: undefined, isPending: false, isError: false };
95+
} else if (
96+
found !== undefined &&
97+
prevEntityRef.current.data !== undefined &&
98+
!equals(found, prevEntityRef.current.data)
99+
) {
100+
// found and ref have a value, compare for equality, if they are not equal, update the ref and return
101+
prevEntityRef.current = { data: found, invalidEntity: undefined, isPending: false, isError: false };
102+
}
103+
104+
return prevEntityRef.current;
105+
});
106+
}

0 commit comments

Comments
 (0)