Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/tame-eagles-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"create-hypergraph": patch
---

Update vite template to use usePublishToPublicSpace

6 changes: 6 additions & 0 deletions .changeset/tidy-ghosts-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@graphprotocol/hypergraph-react": patch
---

Add usePublishToPublicSpace hook

1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm 10.14.0
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import { Button } from '@/components/ui/button';
import { Address } from '@/schema';
import {
HypergraphSpaceProvider,
preparePublish,
publishOps,
useCreateEntity,
useHypergraphApp,
useQuery,
useSpace,
useSpaces,
usePublishToPublicSpace
} from '@graphprotocol/hypergraph-react';
import { createFileRoute } from '@tanstack/react-router';
import { useState } from 'react';
Expand All @@ -34,7 +32,10 @@ function PrivateSpace() {
const [selectedSpace, setSelectedSpace] = useState<string>('');
const createAddress = useCreateEntity(Address);
const [addressName, setAddressName] = useState('');
const { getSmartSessionClient } = useHypergraphApp();
const { mutate: publishToPublicSpace, isPending } = usePublishToPublicSpace({
onSuccess: () => alert('Address published to public space'),
onError: () => alert('Error publishing address to public space')
});

if (!ready) {
return (
Expand All @@ -53,31 +54,6 @@ function PrivateSpace() {
setAddressName('');
};

const publishToPublicSpace = async (address: Address) => {
if (!selectedSpace) {
alert('No space selected');
return;
}
try {
const { ops } = await preparePublish({ entity: address, publicSpace: selectedSpace });
const smartSessionClient = await getSmartSessionClient();
if (!smartSessionClient) {
throw new Error('Missing smartSessionClient');
}
const publishResult = await publishOps({
ops,
space: selectedSpace,
name: 'Publish Address',
walletClient: smartSessionClient,
});
console.log(publishResult, ops);
alert('Address published to public space');
} catch (error) {
console.error(error);
alert('Error publishing address to public space');
}
};

return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8 max-w-4xl">
Expand Down Expand Up @@ -153,8 +129,8 @@ function PrivateSpace() {
</div>

<Button
onClick={() => publishToPublicSpace(address)}
disabled={!selectedSpace}
onClick={() => publishToPublicSpace({ entity: address, spaceId: selectedSpace })}
disabled={!selectedSpace || isPending}
variant="outline"
size="sm"
className="w-full"
Expand Down
25 changes: 7 additions & 18 deletions docs/docs/publishing-public-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,35 +39,24 @@ const { result } = publishOps({
});
```

Here is a full example flow:
Additionally, we export a `usePublishToPublishSpace` hook which abstracts the above functionality into a single function call. This function internally uses React Query's useMutate hook, so you have access to the same state machine and callback functions.

```tsx
import { publishOps, useHypergraphApp } from "@graphprotocol/hypergraph-react";
import { usePublishToPublicSpace, useHypergraphApp } from "@graphprotocol/hypergraph-react";

const MyComponent = ({ publicSpaceId }: { publicSpaceId: string }) => {
const { getSmartSessionClient } = useHypergraphApp();
const { data: events } = useQuery(Event, { mode: "private" });
const { mutate, isPending } = usePublishToPublicSpace();

const publishEvent = async (entity) => {
const smartSessionClient = await getSmartSessionClient();

const { ops } = preparePublish({
entity: entity,
publicSpace: publicSpaceId,
});

const result = await publishOps({
ops,
walletClient: smartSessionClient,
space: publicSpaceId,
name: "Create Event",
});
};
if (isPending) {
return <div>Publishing...</div>
}

return (
<div>
{events.map((event) => (
<button key={event.id} onClick={() => publishEvent(event)}>
<button key={event.id} onClick={() => mutate({ entity: event, spaceId: publicSpaceId })}>
{event.name}
</button>
))}
Expand Down
41 changes: 41 additions & 0 deletions packages/hypergraph-react/src/hooks/usePublishToSpace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Entity } from '@graphprotocol/hypergraph';
import { type UseMutationOptions, useMutation } from '@tanstack/react-query';
import { useHypergraphApp } from '../HypergraphAppContext.js';
import { preparePublish } from '../prepare-publish.js';
import { publishOps } from '../publish-ops.js';
import type { OmitStrict } from '../types.js';

type Variables<S extends Entity.AnyNoContext> = {
entity: S;
spaceId: string;
};

type UsePublishToSpaceOptions = OmitStrict<
UseMutationOptions<Awaited<ReturnType<typeof publishOps>>, Error, Variables<Entity.AnyNoContext>, unknown>,
'mutationFn' | 'mutationKey'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omitted the mutation-related options so devs aren't tempted to add their own mutation function.

>;

export function usePublishToPublicSpace(options: UsePublishToSpaceOptions = {}) {
const { getSmartSessionClient } = useHypergraphApp();

return useMutation({
...options,
mutationFn: async ({ entity, spaceId }) => {
Copy link
Copy Markdown
Contributor Author

@baiirun baiirun Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the spaceId parameter to the mutation function so developers don't need to make a unique hook instance or add state for every space they might deploy to.

const { ops } = await preparePublish({
entity,
publicSpace: spaceId,
});
const smartSessionClient = await getSmartSessionClient();
if (!smartSessionClient) {
throw new Error('Missing smartSessionClient');
}

return await publishOps({
ops,
space: spaceId,
name: 'Published entity',
walletClient: smartSessionClient,
});
},
});
}
1 change: 1 addition & 0 deletions packages/hypergraph-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export { useExternalSpaceInbox } from './hooks/useExternalSpaceInbox.js';
export { useOwnAccountInbox } from './hooks/useOwnAccountInbox.js';
export { useOwnSpaceInbox } from './hooks/useOwnSpaceInbox.js';
export { usePublicAccountInboxes } from './hooks/usePublicAccountInboxes.js';
export { usePublishToPublicSpace } from './hooks/usePublishToSpace.js';
export { generateDeleteOps as _generateDeleteOps } from './internal/generate-delete-ops.js';
export { useCreateEntityPublic as _useCreateEntityPublic } from './internal/use-create-entity-public.js';
export { useDeleteEntityPublic as _useDeleteEntityPublic } from './internal/use-delete-entity-public.js';
Expand Down
2 changes: 2 additions & 0 deletions packages/hypergraph-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Op } from '@graphprotocol/grc-20';
import type { Entity } from '@graphprotocol/hypergraph';
import type * as Schema from 'effect/Schema';

export type OmitStrict<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
Copy link
Copy Markdown
Contributor Author

@baiirun baiirun Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this types utility which I add to basically every codebase. Makes it so Omit is type safe.

type Banana = {
    id: string;
    isFruit: boolean;
}

// Will error as "isVegetable" doesn't exist on type Banana
type DerivedBanana = OmitStrict<Banana, "isVegetable">

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 🙏 idk why this isn't just in typescript. annoys the hell out of me


export type EntityLike = {
id: string;
[key: string]: unknown;
Expand Down
Loading