Skip to content

Commit 533a620

Browse files
committed
add first draft
1 parent ad11615 commit 533a620

File tree

3 files changed

+234
-56
lines changed

3 files changed

+234
-56
lines changed

apps/events/src/routes/space/$spaceId.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { DevTool } from '@/components/dev-tool';
77
import { Todos } from '@/components/todos';
88
import { Button } from '@/components/ui/button';
99
import { availableAccounts } from '@/lib/availableAccounts';
10-
import { useEffect } from 'react';
10+
import { useEffect, useState } from 'react';
1111
import { getAddress } from 'viem';
1212

1313
export const Route = createFileRoute('/space/$spaceId')({
@@ -23,6 +23,7 @@ function Space() {
2323
subscribeToSpace({ spaceId });
2424
}
2525
}, [loading, subscribeToSpace, spaceId]);
26+
const [show2ndTodos, setShow2ndTodos] = useState(false);
2627

2728
const space = spaces.find((space) => space.id === spaceId);
2829

@@ -38,6 +39,7 @@ function Space() {
3839
<div className="flex flex-col gap-4 max-w-screen-sm mx-auto py-8">
3940
<HypergraphSpace.HypergraphProvider space={spaceId}>
4041
<Todos />
42+
{show2ndTodos && <Todos />}
4143
<h3 className="text-xl font-bold">Invite people</h3>
4244
<div className="flex flex-row gap-2">
4345
{availableAccounts.map((invitee) => {
@@ -56,8 +58,9 @@ function Space() {
5658
);
5759
})}
5860
</div>
59-
<div className="mt-12">
61+
<div className="mt-12 flex flex-row gap-2">
6062
<DevTool spaceId={spaceId} />
63+
<Button onClick={() => setShow2ndTodos((prevShow2ndTodos) => !prevShow2ndTodos)}>Toggle Todos</Button>
6164
</div>
6265
</HypergraphSpace.HypergraphProvider>
6366
</div>

packages/hypergraph-react/src/HypergraphSpaceContext.tsx

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
import type { AnyDocumentId, DocHandle, Repo } from '@automerge/automerge-repo';
44
import { useRepo } from '@automerge/automerge-repo-react-hooks';
55
import { Entity, Utils } from '@graphprotocol/hypergraph';
6+
import { type DocumentContent, subscribeToDocumentChanges } from '@graphprotocol/hypergraph/Entity';
67
import * as Schema from 'effect/Schema';
7-
import { type ReactNode, createContext, useContext, useRef, useSyncExternalStore } from 'react';
8+
import { type ReactNode, createContext, useContext, useEffect, useRef, useState, useSyncExternalStore } from 'react';
89

910
export type HypergraphContext = {
1011
space: string;
1112
repo: Repo;
1213
id: AnyDocumentId;
1314
handle: DocHandle<Entity.DocumentContent>;
15+
unsubscribeChangeListener: () => void;
1416
};
1517

1618
export const HypergraphReactContext = createContext<HypergraphContext | undefined>(undefined);
@@ -30,15 +32,28 @@ export function HypergraphProvider({ space, children }: { space: string; childre
3032

3133
let current = ref.current;
3234
if (current === undefined || space !== current.space || repo !== current.repo) {
35+
current?.unsubscribeChangeListener(); // unsubscribe from the previous space when switching to a new space
36+
3337
const id = Utils.idToAutomergeId(space) as AnyDocumentId;
38+
const handle = repo.find<DocumentContent>(id);
39+
const unsubscribeChangeListener = subscribeToDocumentChanges(handle);
40+
3441
current = ref.current = {
3542
space,
3643
repo,
3744
id,
38-
handle: repo.find(id),
45+
handle,
46+
unsubscribeChangeListener,
3947
};
4048
}
4149

50+
// biome-ignore lint/correctness/useExhaustiveDependencies: no need for dependencies as the unsubscribe is called from the ref
51+
useEffect(() => {
52+
return () => {
53+
current?.unsubscribeChangeListener(); // unsubscribe from the previous space when the component unmounts
54+
};
55+
}, []);
56+
4257
return <HypergraphReactContext.Provider value={current}>{children}</HypergraphReactContext.Provider>;
4358
}
4459

@@ -59,37 +74,17 @@ export function useDeleteEntity() {
5974

6075
export function useQueryEntities<const S extends Entity.AnyNoContext>(type: S) {
6176
const hypergraph = useHypergraph();
62-
const equal = isEqual(type);
63-
64-
// store as a map of type to array of entities of the type
65-
const prevEntitiesRef = useRef<Readonly<Array<Entity.Entity<S>>>>([]);
66-
67-
const subscribe = (callback: () => void) => {
68-
const handleChange = () => {
69-
callback();
70-
};
71-
72-
const handleDelete = () => {
73-
callback();
74-
};
75-
76-
hypergraph.handle.on('change', handleChange);
77-
hypergraph.handle.on('delete', handleDelete);
77+
const [subscription] = useState(() => {
78+
return Entity.subscribeToFindMany(hypergraph.handle, type);
79+
});
7880

81+
useEffect(() => {
7982
return () => {
80-
hypergraph.handle.off('change', handleChange);
81-
hypergraph.handle.off('delete', handleDelete);
83+
subscription.unsubscribe();
8284
};
83-
};
85+
}, [subscription]);
8486

85-
return useSyncExternalStore<Readonly<Array<Entity.Entity<S>>>>(subscribe, () => {
86-
const filtered = Entity.findMany(hypergraph.handle, type);
87-
if (!equal(filtered, prevEntitiesRef.current)) {
88-
prevEntitiesRef.current = filtered;
89-
}
90-
91-
return prevEntitiesRef.current;
92-
});
87+
return useSyncExternalStore(subscription.listener, subscription.getEntities, () => []);
9388
}
9489

9590
export function useQueryEntity<const S extends Entity.AnyNoContext>(type: S, id: string) {
@@ -135,22 +130,3 @@ export function useQueryEntity<const S extends Entity.AnyNoContext>(type: S, id:
135130
return prevEntityRef.current;
136131
});
137132
}
138-
139-
/** @internal */
140-
const isEqual = <A, E>(type: Schema.Schema<A, E, never>) => {
141-
const equals = Schema.equivalence(type);
142-
143-
return (a: ReadonlyArray<A>, b: ReadonlyArray<A>) => {
144-
if (a.length !== b.length) {
145-
return false;
146-
}
147-
148-
for (let i = 0; i < a.length; i++) {
149-
if (!equals(a[i], b[i])) {
150-
return false;
151-
}
152-
}
153-
154-
return true;
155-
};
156-
};

0 commit comments

Comments
 (0)