Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/suspense-with-fallback-callback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@suspensive/react": patch
---

Support fallback callbacks in `Suspense.with`.
19 changes: 19 additions & 0 deletions packages/react/src/Suspense.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => <Suspend during={100} toShow={label} />
)

render(<Wrapped label={TEXT} />)

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 = () => <div>{TEXT}</div>
const Wrapped = Suspense.with({}, Component)
Expand Down
30 changes: 23 additions & 7 deletions packages/react/src/Suspense.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -12,6 +18,12 @@ export interface SuspenseProps extends ReactSuspenseProps {
clientOnly?: boolean
}

type SuspenseWithProps<TProps> = PropsWithoutChildren<
Omit<SuspenseProps, 'fallback'> & {
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}
Expand All @@ -32,15 +44,19 @@ export const Suspense = Object.assign(
{
displayName: 'Suspense',
with: <TProps extends ComponentProps<ComponentType> = Record<string, never>>(
suspenseProps: PropsWithoutChildren<SuspenseProps>,
suspenseProps: SuspenseWithProps<TProps>,
Component: ComponentType<TProps>
) =>
Object.assign(
(props: TProps) => (
<Suspense {...suspenseProps}>
<Component {...props} />
</Suspense>
),
(props: TProps) => {
const { fallback, ...restSuspenseProps } = suspenseProps

return (
<Suspense {...restSuspenseProps} fallback={typeof fallback === 'function' ? fallback(props) : fallback}>
<Component {...props} />
</Suspense>
)
},
{ displayName: `${Suspense.displayName}.with(${Component.displayName || Component.name || 'Component'})` }
),
}
Expand Down
Loading