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; }