From b1cb9e78c9ad7ea14f77af2c968264d351e93b16 Mon Sep 17 00:00:00 2001
From: Kris Urbas <605420+krzysu@users.noreply.github.com>
Date: Thu, 2 May 2024 11:27:21 +0200
Subject: [PATCH] feat: suspense support for usePublication hook
---
.changeset/smooth-geese-nail.md | 7 +
examples/web/src/profiles/UseProfile.tsx | 2 +-
.../web/src/publications/UsePublication.tsx | 36 +++--
packages/react/src/helpers/reads.ts | 2 +-
packages/react/src/profile/useProfile.ts | 9 +-
.../react/src/publication/usePublication.ts | 132 +++++++++++-------
6 files changed, 124 insertions(+), 64 deletions(-)
create mode 100644 .changeset/smooth-geese-nail.md
diff --git a/.changeset/smooth-geese-nail.md b/.changeset/smooth-geese-nail.md
new file mode 100644
index 000000000..3221b4542
--- /dev/null
+++ b/.changeset/smooth-geese-nail.md
@@ -0,0 +1,7 @@
+---
+"@lens-protocol/react": minor
+"@lens-protocol/react-native": minor
+"@lens-protocol/react-web": minor
+---
+
+**feat:** experimental React Suspense support in `usePublication` hook
diff --git a/examples/web/src/profiles/UseProfile.tsx b/examples/web/src/profiles/UseProfile.tsx
index b9826d3d8..8c94b763a 100644
--- a/examples/web/src/profiles/UseProfile.tsx
+++ b/examples/web/src/profiles/UseProfile.tsx
@@ -4,7 +4,7 @@ import { Suspense, startTransition, useState } from 'react';
import { Loading } from '../components/loading/Loading';
import { ProfileCard } from './components/ProfileCard';
-export function UseProfileInner({ localName }: { localName: string }) {
+function UseProfileInner({ localName }: { localName: string }) {
const { data, error } = useProfile({ forHandle: `lens/${localName}`, suspense: true });
if (error) {
diff --git a/examples/web/src/publications/UsePublication.tsx b/examples/web/src/publications/UsePublication.tsx
index fd61970ee..91e22e492 100644
--- a/examples/web/src/publications/UsePublication.tsx
+++ b/examples/web/src/publications/UsePublication.tsx
@@ -1,26 +1,42 @@
import { publicationId, usePublication } from '@lens-protocol/react-web';
+import { Suspense, startTransition, useState } from 'react';
import { PublicationCard } from '../components/cards';
-import { ErrorMessage } from '../components/error/ErrorMessage';
import { Loading } from '../components/loading/Loading';
-export function UsePublication() {
- const {
- data: publication,
- error,
- loading,
- } = usePublication({ forId: publicationId('0x56-0x02') });
+function UsePublicationInner({ id }: { id: string }) {
+ const { data, error } = usePublication({ forId: publicationId(id), suspense: true });
+
+ if (error) {
+ return
Publication not found.
;
+ }
- if (loading) return ;
+ return ;
+}
- if (error) return ;
+export function UsePublication() {
+ const [id, setId] = useState('0x36-0x3f');
+
+ const update = (event: React.ChangeEvent) =>
+ startTransition(() => {
+ if (event.target.value.length > 0) {
+ setId(event.target.value);
+ }
+ });
return (
usePublication
-
+
+
+
+
}>
+
+
);
}
diff --git a/packages/react/src/helpers/reads.ts b/packages/react/src/helpers/reads.ts
index 21d4a7e44..408f75a29 100644
--- a/packages/react/src/helpers/reads.ts
+++ b/packages/react/src/helpers/reads.ts
@@ -131,7 +131,7 @@ export function useSuspenseReadResult;
/**
- * `useProfile` is a React hook that allows you to fetch a profile from the Lens API.
+ * Fetch a profile by either its full handle or id.
*
* @example
* ```ts
@@ -67,7 +67,8 @@ export type UseProfileResult =
*
* ```ts
* const { data } = useProfile({
- * forHandle: 'lens/stani'
+ * forHandle: 'lens/stani',
+ * suspense: true,
* });
*
* console.log(data.id);
@@ -88,9 +89,7 @@ export function useProfile(
export function useProfile({
suspense = false,
...request
-}: UseProfileArgs):
- | ReadResult
- | SuspenseResultWithError {
+}: UseProfileArgs): UseProfileResult {
invariant(
request.forProfileId === undefined || request.forHandle === undefined,
"Only one of 'forProfileId' or 'forHandle' should be provided to 'useProfile' hook",
diff --git a/packages/react/src/publication/usePublication.ts b/packages/react/src/publication/usePublication.ts
index 36e084713..c63903b9b 100644
--- a/packages/react/src/publication/usePublication.ts
+++ b/packages/react/src/publication/usePublication.ts
@@ -1,20 +1,43 @@
import {
AnyPublication,
+ PublicationDocument,
PublicationRequest,
+ PublicationVariables,
UnspecifiedError,
- usePublication as usePublicationHook,
} from '@lens-protocol/api-bindings';
import { OneOf, invariant } from '@lens-protocol/shared-kernel';
import { NotFoundError } from '../NotFoundError';
import { useLensApolloClient } from '../helpers/arguments';
-import { ReadResult, useReadResult } from '../helpers/reads';
+import {
+ ReadResult,
+ SuspenseEnabled,
+ SuspenseResultWithError,
+ useSuspendableQuery,
+} from '../helpers/reads';
import { useFragmentVariables } from '../helpers/variables';
+// function isValidPublicationId(id: string) {
+// return id.includes('-');
+// }
+
+function publicationNotFound({ forId, forTxHash }: UsePublicationArgs) {
+ return new NotFoundError(
+ forId
+ ? `Publication with id ${forId} was not found`
+ : `Publication with txHash ${forTxHash ? forTxHash : ''} was not found`,
+ );
+}
+
/**
* {@link usePublication} hook arguments
*/
-export type UsePublicationArgs = OneOf;
+export type UsePublicationArgs = OneOf &
+ SuspenseEnabled;
+
+export type UsePublicationResult =
+ | ReadResult
+ | SuspenseResultWithError;
/**
* Fetch a publication by either its publication id or transaction hash.
@@ -26,66 +49,81 @@ export type UsePublicationArgs = OneOf;
* });
* ```
*
+ * ## Basic Usage
+ *
+ * Get Publication by Id:
+ *
+ * ```ts
+ * const { data, error, loading } = usePublication({
+ * forId: '0x04-0x0b',
+ * });
+ * ```
+ *
+ * Get Publication by Transaction Hash:
+ *
+ * ```ts
+ * const { data, error, loading } = usePublication({
+ * forTxHash: '0xcd0655e8d1d131ebfc72fa5ebff6ed0430e6e39e729af1a81da3b6f33822a6ff',
+ * });
+ * ```
+ *
+ * ## Suspense Enabled
+ *
+ * You can enable suspense mode to suspend the component until the session data is available.
+ *
+ * ```ts
+ * const { data } = usePublication({
+ * forId: '0x04-0x0b',
+ * suspense: true,
+ * });
+ *
+ * console.log(data.id);
+ * ```
+ *
* @category Publications
* @group Hooks
+ *
* @param args - {@link UsePublicationArgs}
*/
export function usePublication({
forId,
forTxHash,
-}: UsePublicationArgs): ReadResult {
+}: UsePublicationArgs): ReadResult;
+export function usePublication(
+ args: UsePublicationArgs,
+): SuspenseResultWithError;
+export function usePublication({
+ suspense = false,
+ ...request
+}: UsePublicationArgs): UsePublicationResult {
invariant(
- forId === undefined || forTxHash === undefined,
+ request.forId === undefined || request.forTxHash === undefined,
"Only one of 'forId' or 'forTxHash' should be provided to 'usePublication' hook",
);
- const { data, error, loading } = useReadResult(
- usePublicationHook(
- useLensApolloClient({
- variables: useFragmentVariables({
- request: {
- ...(forId && { forId }),
- ...(forTxHash && { forTxHash }),
- },
- }),
- fetchPolicy: 'cache-and-network',
- // leverage cache content if possible
- nextFetchPolicy: 'cache-first',
- }),
- ),
- );
-
- if (loading) {
- return {
- data: undefined,
- error: undefined,
- loading: true,
- };
- }
+ const result = useSuspendableQuery({
+ suspense,
+ query: PublicationDocument,
+ options: useLensApolloClient({
+ variables: useFragmentVariables({ request }),
+ fetchPolicy: 'cache-and-network',
+ nextFetchPolicy: 'cache-first',
+ }),
+ });
- if (error) {
- return {
- data: undefined,
- error,
- loading: false,
- };
- }
+ // if (request.forId && !isValidPublicationId(request.forId)) {
+ // return {
+ // data: undefined,
+ // error: publicationNotFound(request),
+ // };
+ // }
- if (data === null) {
+ if (result.data === null) {
return {
data: undefined,
- error: new NotFoundError(
- forId
- ? `Publication with id ${forId} was not found`
- : `Publication with txHash ${forTxHash ? forTxHash : ''} was not found`,
- ),
- loading: false,
+ error: publicationNotFound(request),
};
}
- return {
- data,
- error: undefined,
- loading: false,
- };
+ return result as UsePublicationResult;
}