diff --git a/.changeset/suspense-with-fallback-callback.md b/.changeset/suspense-with-fallback-callback.md new file mode 100644 index 000000000..b9f534038 --- /dev/null +++ b/.changeset/suspense-with-fallback-callback.md @@ -0,0 +1,5 @@ +--- +"@suspensive/react": patch +--- + +Support fallback callbacks in `Suspense.with`. diff --git a/packages/react/src/Suspense.spec.tsx b/packages/react/src/Suspense.spec.tsx index 282008d0d..c7dcc6114 100644 --- a/packages/react/src/Suspense.spec.tsx +++ b/packages/react/src/Suspense.spec.tsx @@ -120,6 +120,25 @@ describe('Suspense.with', () => { expect(screen.queryByText(FALLBACK)).not.toBeInTheDocument() }) + it('should render fallback callback with wrapped component props', async () => { + const Wrapped = Suspense.with( + { + fallback: ({ label }: { label: string }) => <>{label} is loading, + }, + ({ label }: { label: string }) => + ) + + render() + + expect(screen.queryByText(`${TEXT} is loading`)).toBeInTheDocument() + expect(screen.queryByText(TEXT)).not.toBeInTheDocument() + + await act(() => vi.advanceTimersByTime(100)) + + expect(screen.queryByText(`${TEXT} is loading`)).not.toBeInTheDocument() + expect(screen.queryByText(TEXT)).toBeInTheDocument() + }) + it('should use default suspenseProps when {} is provided', () => { const Component = () =>
{TEXT}
const Wrapped = Suspense.with({}, Component) diff --git a/packages/react/src/Suspense.tsx b/packages/react/src/Suspense.tsx index 31fff0ca8..ee9a0d379 100644 --- a/packages/react/src/Suspense.tsx +++ b/packages/react/src/Suspense.tsx @@ -1,5 +1,11 @@ 'use client' -import { type ComponentProps, type ComponentType, type SuspenseProps as ReactSuspenseProps, useContext } from 'react' +import { + type ComponentProps, + type ComponentType, + type ReactNode, + type SuspenseProps as ReactSuspenseProps, + useContext, +} from 'react' import { SuspenseDefaultPropsContext } from './contexts/DefaultPropsContexts' import type { PropsWithoutChildren } from './utility-types/PropsWithoutChildren' import { defineSuspense } from './utils/defineSuspense' @@ -12,6 +18,12 @@ export interface SuspenseProps extends ReactSuspenseProps { clientOnly?: boolean } +type SuspenseWithProps = PropsWithoutChildren< + Omit & { + fallback?: SuspenseProps['fallback'] | ((props: TProps) => ReactNode) + } +> + /** * This component is just wrapping React's Suspense. to use Suspense easily in Server-side rendering environment like Next.js * @see {@link https://suspensive.org/docs/react/Suspense Suspensive Docs} @@ -32,15 +44,19 @@ export const Suspense = Object.assign( { displayName: 'Suspense', with: = Record>( - suspenseProps: PropsWithoutChildren, + suspenseProps: SuspenseWithProps, Component: ComponentType ) => Object.assign( - (props: TProps) => ( - - - - ), + (props: TProps) => { + const { fallback, ...restSuspenseProps } = suspenseProps + + return ( + + + + ) + }, { displayName: `${Suspense.displayName}.with(${Component.displayName || Component.name || 'Component'})` } ), }