From 697eca13a158374d04320f83ef54bd2b55c7e1f9 Mon Sep 17 00:00:00 2001 From: Cesare Naldi Date: Tue, 21 May 2024 13:29:44 +0200 Subject: [PATCH] feat: Suspense support to useExplorePublications, useExploreProfiles, useRecommendedProfiles hooks --- .changeset/seven-pugs-own.md | 7 + .../web/src/discovery/UseExploreProfiles.tsx | 9 +- .../src/discovery/UseExplorePublications.tsx | 9 +- .../src/discovery/UseRecommendedProfiles.tsx | 34 ++--- .../react/src/authentication/useSession.ts | 2 +- .../react/src/discovery/useExploreProfiles.ts | 112 +++++++++++----- .../src/discovery/useExplorePublications.ts | 123 ++++++++++++------ .../src/discovery/useRecommendedProfiles.ts | 69 +++++++--- .../react/src/discovery/useSearchProfiles.ts | 4 +- .../src/discovery/useSearchPublications.ts | 78 +++++------ packages/react/src/helpers/tasks.ts | 2 +- packages/react/src/misc/useCurrencies.ts | 4 +- .../react/src/profile/useProfileManagers.ts | 4 +- 13 files changed, 276 insertions(+), 181 deletions(-) create mode 100644 .changeset/seven-pugs-own.md diff --git a/.changeset/seven-pugs-own.md b/.changeset/seven-pugs-own.md new file mode 100644 index 0000000000..8342ee4bea --- /dev/null +++ b/.changeset/seven-pugs-own.md @@ -0,0 +1,7 @@ +--- +"@lens-protocol/react": minor +"@lens-protocol/react-native": minor +"@lens-protocol/react-web": minor +--- + +**feat:** add React Suspense support to `useExplorePublications`, `useExploreProfiles`, `useRecommendedProfiles` hooks diff --git a/examples/web/src/discovery/UseExploreProfiles.tsx b/examples/web/src/discovery/UseExploreProfiles.tsx index 3cc60d42af..e51a4961f7 100644 --- a/examples/web/src/discovery/UseExploreProfiles.tsx +++ b/examples/web/src/discovery/UseExploreProfiles.tsx @@ -1,21 +1,16 @@ import { useExploreProfiles, ExploreProfilesOrderByType } from '@lens-protocol/react-web'; -import { ErrorMessage } from '../components/error/ErrorMessage'; -import { Loading } from '../components/loading/Loading'; import { useInfiniteScroll } from '../hooks/useInfiniteScroll'; import { ProfileCard } from '../profiles/components/ProfileCard'; export function UseExploreProfiles() { - const { data, error, loading, hasMore, observeRef } = useInfiniteScroll( + const { data, hasMore, observeRef } = useInfiniteScroll( useExploreProfiles({ orderBy: ExploreProfilesOrderByType.LatestCreated, + suspense: true, }), ); - if (loading) return ; - - if (error) return ; - if (data.length === 0) return

No items

; return ( diff --git a/examples/web/src/discovery/UseExplorePublications.tsx b/examples/web/src/discovery/UseExplorePublications.tsx index 0718a26452..d231649209 100644 --- a/examples/web/src/discovery/UseExplorePublications.tsx +++ b/examples/web/src/discovery/UseExplorePublications.tsx @@ -7,8 +7,6 @@ import { } from '@lens-protocol/react-web'; import { PublicationCard } from '../components/cards'; -import { ErrorMessage } from '../components/error/ErrorMessage'; -import { Loading } from '../components/loading/Loading'; import { useInfiniteScroll } from '../hooks/useInfiniteScroll'; import { formatAmount, formatFiatAmount } from '../utils/formatAmount'; @@ -45,20 +43,15 @@ function PublicationCollectPolicy({ publication }: { publication: ExplorePublica export function UseExplorePublications() { const { data: publications, - error, - loading, hasMore, observeRef, } = useInfiniteScroll( useExplorePublications({ orderBy: ExplorePublicationsOrderByType.Latest, + suspense: true, }), ); - if (loading) return ; - - if (error) return ; - return (

diff --git a/examples/web/src/discovery/UseRecommendedProfiles.tsx b/examples/web/src/discovery/UseRecommendedProfiles.tsx index 9840d3655d..ec7b9ffc49 100644 --- a/examples/web/src/discovery/UseRecommendedProfiles.tsx +++ b/examples/web/src/discovery/UseRecommendedProfiles.tsx @@ -5,21 +5,18 @@ import { } from '@lens-protocol/react-web'; import { RequireProfileSession } from '../components/auth'; -import { ErrorMessage } from '../components/error/ErrorMessage'; -import { Loading } from '../components/loading/Loading'; import { useInfiniteScroll } from '../hooks/useInfiniteScroll'; import { ProfileCard } from '../profiles/components/ProfileCard'; function UseRecommendedProfilesInner({ profileId }: { profileId: ProfileId }) { const { data: profiles, - error, - loading, hasMore, observeRef, } = useInfiniteScroll( useRecommendedProfiles({ for: profileId, + suspense: true, }), ); @@ -29,21 +26,20 @@ function UseRecommendedProfilesInner({ profileId }: { profileId: ProfileId }) { void dismiss({ profileIds: [id] }); }; - if (loading) return ; - - if (error) return ; - return (
- {profiles.map((p) => ( -
- +

+ useRecommendedProfiles +

+
+ {profiles.map((p) => ( + -
- ))} + ))} +
{hasMore &&

Loading more...

}
@@ -52,14 +48,8 @@ function UseRecommendedProfilesInner({ profileId }: { profileId: ProfileId }) { export function UseRecommendedProfiles() { return ( -
-

- useRecommendedProfiles & useDismissRecommendedProfiles -

- - - {({ profile }) => } - -
+ + {({ profile }) => } + ); } diff --git a/packages/react/src/authentication/useSession.ts b/packages/react/src/authentication/useSession.ts index 976e8dc992..7f9289a1c5 100644 --- a/packages/react/src/authentication/useSession.ts +++ b/packages/react/src/authentication/useSession.ts @@ -99,7 +99,7 @@ export type UseSessionArgs = SuspenseEnabled; * function Page() { * const { data, error, loading } = useSession(); * - * if (loading) return

Loading...

; + * if (loading) return ; * * if (error) return

Something went wrong.

; * diff --git a/packages/react/src/discovery/useExploreProfiles.ts b/packages/react/src/discovery/useExploreProfiles.ts index 1c204b0a14..f025a463d2 100644 --- a/packages/react/src/discovery/useExploreProfiles.ts +++ b/packages/react/src/discovery/useExploreProfiles.ts @@ -1,61 +1,101 @@ import { + ExploreProfilesDocument, ExploreProfilesOrderByType, ExploreProfilesRequest, + ExploreProfilesWhere, Profile, - useExploreProfiles as useBaseExploreProfilesQuery, } from '@lens-protocol/api-bindings'; import { useLensApolloClient } from '../helpers/arguments'; -import { PaginatedArgs, PaginatedReadResult, usePaginatedReadResult } from '../helpers/reads'; +import { PaginatedArgs, PaginatedReadResult } from '../helpers/reads'; +import { + SuspendablePaginatedResult, + SuspenseEnabled, + SuspensePaginatedResult, + useSuspendablePaginatedQuery, +} from '../helpers/suspense'; import { useFragmentVariables } from '../helpers/variables'; +/** + * {@link useExploreProfiles} hook arguments + */ export type UseExploreProfilesArgs = PaginatedArgs; +export type { ExploreProfilesRequest, ExploreProfilesWhere }; + /** - * `useExploreProfiles` is a paginated hook that lets you discover new profiles based on a defined criteria - * - * @category Discovery - * @group Hooks - * @param args - {@link UseExploreProfilesArgs} + * {@link useExploreProfiles} hook arguments with Suspense support + */ +export type UseSuspenseExploreProfilesArgs = SuspenseEnabled; + +/** + * Discover new profiles based on a defined criteria. * - * @example - * Explore the latest created profiles * ```tsx - * import { useExploreProfiles, ExploreProfilesOrderByType } from '@lens-protocol/react'; + * const { data, error, loading } = useExploreProfiles({ + * orderBy: ExploreProfilesOrderByType.LatestCreated, + * }); + * + * if (loading) return ; + * + * if (error) return ; * - * function ExploreProfiles() { - * const { data, error, loading } = useExploreProfiles({ - * orderBy: ExploreProfilesOrderByType.LatestCreated, - * }); + * return ( + * <> + * {data.map((profile) => ( + * + * ))} + * + * ); + * ``` + * + * @category Discovery + * @group Hooks + */ +export function useExploreProfiles(args?: UseExploreProfilesArgs): PaginatedReadResult; + +/** + * Discover new profiles based on a defined criteria. * - * if (loading) return

Loading...

; + * This signature supports [React Suspense](https://react.dev/reference/react/Suspense). * - * if (error) return

Error: {error.message}

; + * ```ts + * const { data } = useExploreProfiles({ + * orderBy: ExploreProfilesOrderByType.LatestCreated, + * suspense: true, + * ); * - * return ( - *
    - * {data.map((profile) => ( - *
  • {profile.handle}
  • - * ))} - *
- * ); - * } + * console.log(data); * ``` + * + * @experimental This API can change without notice + * @category Discovery + * @group Hooks */ export function useExploreProfiles( - { where, limit, orderBy = ExploreProfilesOrderByType.LatestCreated }: UseExploreProfilesArgs = { + args: UseSuspenseExploreProfilesArgs, +): SuspensePaginatedResult; + +export function useExploreProfiles( + { + where, + limit, + orderBy = ExploreProfilesOrderByType.LatestCreated, + suspense = false, + }: UseExploreProfilesArgs & { suspense?: boolean } = { orderBy: ExploreProfilesOrderByType.LatestCreated, + suspense: false, }, -): PaginatedReadResult { - return usePaginatedReadResult( - useBaseExploreProfilesQuery( - useLensApolloClient({ - variables: useFragmentVariables({ - limit, - where, - orderBy, - }), +): SuspendablePaginatedResult { + return useSuspendablePaginatedQuery({ + suspense, + query: ExploreProfilesDocument, + options: useLensApolloClient({ + variables: useFragmentVariables({ + limit, + where, + orderBy, }), - ), - ); + }), + }); } diff --git a/packages/react/src/discovery/useExplorePublications.ts b/packages/react/src/discovery/useExplorePublications.ts index 3a4597443a..b5fc90e419 100644 --- a/packages/react/src/discovery/useExplorePublications.ts +++ b/packages/react/src/discovery/useExplorePublications.ts @@ -1,66 +1,109 @@ import { + ExplorePublication, ExplorePublicationRequest, + ExplorePublicationsDocument, ExplorePublicationsOrderByType, - useExplorePublications as useUnderlyingQuery, - ExplorePublication, + ExplorePublicationsWhere, } from '@lens-protocol/api-bindings'; import { useLensApolloClient } from '../helpers/arguments'; -import { PaginatedArgs, PaginatedReadResult, usePaginatedReadResult } from '../helpers/reads'; +import { PaginatedArgs, PaginatedReadResult } from '../helpers/reads'; +import { + SuspendablePaginatedResult, + SuspenseEnabled, + SuspensePaginatedResult, + useSuspendablePaginatedQuery, +} from '../helpers/suspense'; import { useFragmentVariables } from '../helpers/variables'; +/** + * {@link useExplorePublications} hook arguments + */ export type UseExplorePublicationsArgs = PaginatedArgs; +export type { ExplorePublicationRequest, ExplorePublicationsWhere }; + /** - * `useExplorePublications` is a paginated hook that lets you discover new publications base on a defined criteria + * {@link useExplorePublications} hook arguments with Suspense support * - * @category Discovery - * @group Hooks - * @param args - {@link UseExplorePublicationsArgs} + * @experimental This API can change without notice + */ +export type UseSuspenseExplorePublicationsArgs = SuspenseEnabled; + +/** + * Discover new publications base on a defined criteria. * - * @example - * Explore publications of type post with the most comments * ```tsx - * import { useExplorePublications, ExplorePublicationsOrderByType, ExplorePublicationType } from '@lens-protocol/react'; + * const { data, error, loading } = useExplorePublications( + * where: { + * publicationTypes: [ExplorePublicationType.Post], + * }, + * orderBy: ExplorePublicationsOrderByType.TopCommented, + * ); + * + * if (loading) return ; + * + * if (error) return ; * - * function ExplorePublications() { - * const { data, error, loading } = useExplorePublications( - * where: { - * publicationTypes: [ExplorePublicationType.Post], - * }, - * orderBy: ExplorePublicationsOrderByType.TopCommented, + * return ( + * <> + * {data.map((publication) => ( + * + * ))} + * * ); + * ``` + * + * @category Discovery + * @group Hooks + */ +export function useExplorePublications( + args?: UseExplorePublicationsArgs, +): PaginatedReadResult; + +/** + * Discover new publications base on a defined criteria. * - * if (loading) return

Loading...

; + * This signature supports [React Suspense](https://react.dev/reference/react/Suspense). * - * if (error) return

Error: {error.message}

; + * ```ts + * const { data } = useExplorePublications( + * where: { + * publicationTypes: [ExplorePublicationType.Post], + * }, + * orderBy: ExplorePublicationsOrderByType.TopCommented, + * suspense: true, + * ); * - * return ( - *
    - * {data.map((publication) => ( - *
  • - * // render publication details - *
  • - * ))} - *
- * ); - * } + * console.log(data); * ``` + * + * @experimental This API can change without notice + * @category Discovery + * @group Hooks */ export function useExplorePublications( - { where, orderBy = ExplorePublicationsOrderByType.Latest }: UseExplorePublicationsArgs = { + args: UseSuspenseExplorePublicationsArgs, +): SuspensePaginatedResult; + +export function useExplorePublications( + { + orderBy = ExplorePublicationsOrderByType.Latest, + where, + suspense = false, + }: UseExplorePublicationsArgs & { suspense?: boolean } = { orderBy: ExplorePublicationsOrderByType.Latest, }, -): PaginatedReadResult { - return usePaginatedReadResult( - useUnderlyingQuery( - useLensApolloClient({ - variables: useFragmentVariables({ - where, - orderBy, - statsFor: where?.metadata?.publishedOn, - }), +): SuspendablePaginatedResult { + return useSuspendablePaginatedQuery({ + suspense, + query: ExplorePublicationsDocument, + options: useLensApolloClient({ + variables: useFragmentVariables({ + where, + orderBy, + statsFor: where?.metadata?.publishedOn, }), - ), - ); + }), + }); } diff --git a/packages/react/src/discovery/useRecommendedProfiles.ts b/packages/react/src/discovery/useRecommendedProfiles.ts index 5e112d1bd9..d8497a8c41 100644 --- a/packages/react/src/discovery/useRecommendedProfiles.ts +++ b/packages/react/src/discovery/useRecommendedProfiles.ts @@ -1,11 +1,17 @@ import { Profile, + ProfileRecommendationsDocument, ProfileRecommendationsRequest, - useProfileRecommendations as useProfileRecommendationsHook, } from '@lens-protocol/api-bindings'; import { useLensApolloClient } from '../helpers/arguments'; -import { PaginatedArgs, PaginatedReadResult, usePaginatedReadResult } from '../helpers/reads'; +import { PaginatedArgs, PaginatedReadResult } from '../helpers/reads'; +import { + SuspendablePaginatedResult, + SuspenseEnabled, + SuspensePaginatedResult, + useSuspendablePaginatedQuery, +} from '../helpers/suspense'; import { useFragmentVariables } from '../helpers/variables'; /** @@ -13,27 +19,60 @@ import { useFragmentVariables } from '../helpers/variables'; */ export type UseRecommendedProfilesArgs = PaginatedArgs; +export type { ProfileRecommendationsRequest }; + /** - * `useRecommendedProfiles` is a paginated hook that lets you fetch recommended profiles. - * - * @category Discovery - * @group Hooks + * {@link useRecommendedProfiles} hook arguments with Suspense support + */ +export type UseSuspenseRecommendedProfilesArgs = SuspenseEnabled; + +/** + * Provides profile recommendations based on user's social engagement and machine learning predictions. * - * @example * ```tsx * const { data, loading, error } = useRecommendedProfiles({ * for: '0x123', * }); * ``` + * + * @category Discovery + * @group Hooks */ export function useRecommendedProfiles( args: UseRecommendedProfilesArgs, -): PaginatedReadResult { - return usePaginatedReadResult( - useProfileRecommendationsHook( - useLensApolloClient({ - variables: useFragmentVariables(args), - }), - ), - ); +): PaginatedReadResult; + +/** + * Provides profile recommendations based on user's social engagement and machine learning predictions. + * + * This signature supports [React Suspense](https://react.dev/reference/react/Suspense). + * + * ```ts + * const { data } = useRecommendedProfiles({ + * for: '0x123', + * suspense: true + * }); + * + * console.log(data); + * ``` + * + * @experimental This API can change without notice + * @category Discovery + * @group Hooks + */ +export function useRecommendedProfiles( + args: UseSuspenseRecommendedProfilesArgs, +): SuspensePaginatedResult; + +export function useRecommendedProfiles({ + suspense = false, + ...args +}: UseRecommendedProfilesArgs & { suspense?: boolean }): SuspendablePaginatedResult { + return useSuspendablePaginatedQuery({ + suspense, + query: ProfileRecommendationsDocument, + options: useLensApolloClient({ + variables: useFragmentVariables(args), + }), + }); } diff --git a/packages/react/src/discovery/useSearchProfiles.ts b/packages/react/src/discovery/useSearchProfiles.ts index c7e9402910..2e7d786da7 100644 --- a/packages/react/src/discovery/useSearchProfiles.ts +++ b/packages/react/src/discovery/useSearchProfiles.ts @@ -36,9 +36,9 @@ export type UseSuspenseSearchProfilesArgs = SuspenseEnabledLoading...

; + * if (loading) return ; * - * if (error) return

Error: {error.message}

; + * if (error) return ; * * return ( *
    diff --git a/packages/react/src/discovery/useSearchPublications.ts b/packages/react/src/discovery/useSearchPublications.ts index 385e2eb11e..b2825f61cf 100644 --- a/packages/react/src/discovery/useSearchPublications.ts +++ b/packages/react/src/discovery/useSearchPublications.ts @@ -30,60 +30,48 @@ export type { PublicationSearchRequest, PublicationSearchWhere }; export type UseSuspenseSearchPublicationsArgs = SuspenseEnabled; /** - * Search for publications based on a defined criteria + * Search for publications based on a defined criteria. * * Search for publications with content that contains "foo" * ```tsx - * import { useSearchPublications } from '@lens-protocol/react'; + * const { data, error, loading } = useSearchPublications({ query: 'foo' }); * - * function SearchPublication() { - * const { data, error, loading } = useSearchPublications({ query: 'foo' }); + * if (loading) return ; * - * if (loading) return

    Loading...

    ; + * if (error) return ; * - * if (error) return

    Error: {error.message}

    ; - * - * return ( - *
      - * {data.map((publication) => ( - *
    • - * // render publication details - *
    • - * ))} - *
    - * ); - * } + * return ( + * <> + * {data.map((publication) => ( + * + * ))} + * + * ); * ``` * * Search for audio post publications with content that matches a query * ```tsx - * import { useSearchPublications } from '@lens-protocol/react'; - * - * function SearchPublication() { - * const { data, error, loading } = useSearchPublications({ - * query, - * where: { - * publicationTypes: [SearchPublicationType.Post], - * metadata: { - * mainContentFocus: [PublicationMetadataMainFocusType.Audio], - * }, - * }, - * }); - * - * if (loading) return

    Loading...

    ; - * - * if (error) return

    Error: {error.message}

    ; - * - * return ( - *
      - * {data.map((publication) => ( - *
    • - * // render publication details - *
    • - * ))} - *
    - * ); - * } + * const { data, error, loading } = useSearchPublications({ + * query: '...', + * where: { + * publicationTypes: [SearchPublicationType.Post], + * metadata: { + * mainContentFocus: [PublicationMetadataMainFocusType.Audio], + * }, + * }, + * }); + * + * if (loading) return ; + * + * if (error) return ; + * + * return ( + * <> + * {data.map((publication) => ( + * + * ))} + * + * ); * ``` * * @category Discovery @@ -94,7 +82,7 @@ export function useSearchPublications( ): PaginatedReadResult; /** - * Search for publications based on a defined criteria + * Search for publications based on a defined criteria. * * This signature supports [React Suspense](https://react.dev/reference/react/Suspense). * diff --git a/packages/react/src/helpers/tasks.ts b/packages/react/src/helpers/tasks.ts index 08997352a6..128ca6ec4a 100644 --- a/packages/react/src/helpers/tasks.ts +++ b/packages/react/src/helpers/tasks.ts @@ -86,7 +86,7 @@ export type DeferredTaskState = * // data === undefined on first call * // data === TData from previous successful call * // error === undefined - * return

    Loading...

    ; + * return ; * } * * if (error) { diff --git a/packages/react/src/misc/useCurrencies.ts b/packages/react/src/misc/useCurrencies.ts index ece4b69f6a..217975408c 100644 --- a/packages/react/src/misc/useCurrencies.ts +++ b/packages/react/src/misc/useCurrencies.ts @@ -32,9 +32,9 @@ export type UseCurrenciesArgs = PaginatedArgs; * function CurrencySelector({ onChange }: { onChange: (currency: Erc20) => void }) { * const { data: currencies, error, loading } = useCurrencies(); * - * if (loading) return

    Loading...

    ; + * if (loading) return ; * - * if (error) return

    Error: {error.message}

    ; + * if (error) return ; * * const handleChange = (event: React.ChangeEvent) => { * const currency = currencies.find((c) => c.symbol === event.target.value); diff --git a/packages/react/src/profile/useProfileManagers.ts b/packages/react/src/profile/useProfileManagers.ts index 7935cb6ecb..12ad5838f7 100644 --- a/packages/react/src/profile/useProfileManagers.ts +++ b/packages/react/src/profile/useProfileManagers.ts @@ -26,11 +26,11 @@ export type UseProfileManagersArgs = PaginatedArgs<{ * }); * * if (loading) { - * return

    Loading...

    ; + * return ; * } * * if (error) { - * return

    Error: {error.message}

    ; + * return ; * } * * return (