Skip to content

Commit

Permalink
Merge pull request #930 from lens-protocol/T-20234/usepublication
Browse files Browse the repository at this point in the history
feat: suspense support for usePublication hook
  • Loading branch information
cesarenaldi committed May 8, 2024
2 parents aa460c7 + b1cb9e7 commit 9eab6cb
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 64 deletions.
7 changes: 7 additions & 0 deletions .changeset/smooth-geese-nail.md
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion examples/web/src/profiles/UseProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
36 changes: 26 additions & 10 deletions examples/web/src/publications/UsePublication.tsx
Original file line number Diff line number Diff line change
@@ -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 <p>Publication not found.</p>;
}

if (loading) return <Loading />;
return <PublicationCard publication={data} />;
}

if (error) return <ErrorMessage error={error} />;
export function UsePublication() {
const [id, setId] = useState('0x36-0x3f');

const update = (event: React.ChangeEvent<HTMLInputElement>) =>
startTransition(() => {
if (event.target.value.length > 0) {
setId(event.target.value);
}
});

return (
<div>
<h1>
<code>usePublication</code>
</h1>
<PublicationCard publication={publication} />

<label>
Publication ID <input type="text" name="localName" defaultValue={id} onChange={update} />
</label>

<Suspense fallback={<Loading />}>
<UsePublicationInner id={id} />
</Suspense>
</div>
);
}
2 changes: 1 addition & 1 deletion packages/react/src/helpers/reads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function useSuspenseReadResult<TResult, TVariables extends OperationVaria
}

return {
data: data?.result ?? never('Data should be available in suspense mode.'),
data: data?.result,
};
}

Expand Down
9 changes: 4 additions & 5 deletions packages/react/src/profile/useProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export type UseProfileResult =
| SuspenseResultWithError<Profile, NotFoundError>;

/**
* `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
Expand Down Expand Up @@ -67,7 +67,8 @@ export type UseProfileResult =
*
* ```ts
* const { data } = useProfile({
* forHandle: 'lens/stani'
* forHandle: 'lens/stani',
* suspense: true,
* });
*
* console.log(data.id);
Expand All @@ -88,9 +89,7 @@ export function useProfile(
export function useProfile({
suspense = false,
...request
}: UseProfileArgs<boolean>):
| ReadResult<Profile, NotFoundError | UnspecifiedError>
| SuspenseResultWithError<Profile, NotFoundError> {
}: UseProfileArgs<boolean>): UseProfileResult {
invariant(
request.forProfileId === undefined || request.forHandle === undefined,
"Only one of 'forProfileId' or 'forHandle' should be provided to 'useProfile' hook",
Expand Down
132 changes: 85 additions & 47 deletions packages/react/src/publication/usePublication.ts
Original file line number Diff line number Diff line change
@@ -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<boolean>) {
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<PublicationRequest>;
export type UsePublicationArgs<TSuspense extends boolean = never> = OneOf<PublicationRequest> &
SuspenseEnabled<TSuspense>;

export type UsePublicationResult =
| ReadResult<AnyPublication, NotFoundError | UnspecifiedError>
| SuspenseResultWithError<AnyPublication, NotFoundError>;

/**
* Fetch a publication by either its publication id or transaction hash.
Expand All @@ -26,66 +49,81 @@ export type UsePublicationArgs = OneOf<PublicationRequest>;
* });
* ```
*
* ## 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<AnyPublication, NotFoundError | UnspecifiedError> {
}: UsePublicationArgs<never>): ReadResult<AnyPublication, NotFoundError | UnspecifiedError>;
export function usePublication(
args: UsePublicationArgs<true>,
): SuspenseResultWithError<AnyPublication, NotFoundError>;
export function usePublication({
suspense = false,
...request
}: UsePublicationArgs<boolean>): 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<AnyPublication | null, PublicationVariables>({
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;
}

0 comments on commit 9eab6cb

Please sign in to comment.