👀 Check out the changes in Suspensive v2. read more →
Suspensive with star
Suspensive v2

All in one for React Suspense

All Declarative APIs ready

<Suspense/>, <ErrorBoundary/>, <ErrorBoundaryGroup/>, etc. are provided. Use them easily without any efforts.

Zero peer dependency, Only React

It is simply extensions of react's concepts. Named friendly with originals like just <Suspense/>, <ErrorBoundary/>, <ErrorBoundaryGroup/>.

Suspense in SSR easily

Suspensive provide clientOnly that make developer can adopt React Suspense gradually in Server-side rendering environment.

If you write code without Suspense with TanStack Query, a representative library, you would write it like this.

In this case, you can check isLoading and isError to handle loading and error states, and remove undefined from data in TypeScript.

But let's assume that there are more APIs to query.

If there are more APIs to query, the code to handle the loading state and error state becomes more complicated.

Suspense makes the code concise in terms of type. However, the depth of the component inevitably increases.

useSuspenseQuery can handle loading and error states externally using Suspense and ErrorBoundary. However, since useSuspenseQuery is a hook, the component must be separated to place Suspense and ErrorBoundary in the parent, which causes the depth to increase.

Using Suspensive's SuspenseQuery component, you can avoid the constraints of hooks and write code more easily at the same depth.

  1. Using SuspenseQuery, you can remove depth.

  2. You remove the component called UserInfo, leaving only the presentational component like UserProfile, which makes it easier to test.

const Page = () => {
const userQuery = useQuery(userQueryOptions())
const postsQuery = useQuery({
...postsQueryOptions(),
select: (posts) => posts.filter(({ isPublic }) => isPublic),
})
const promotionsQuery = useQuery(promotionsQueryOptions())
if (
userQuery.isLoading ||
postsQuery.isLoading ||
promotionsQuery.isLoading
) {
return 'loading...'
}
if (userQuery.isError || postsQuery.isError || promotionsQuery.isError) {
return 'error'
}
return (
<Fragment>
<UserProfile {...userQuery.data} />
{postsQuery.data.map((post) => (
<PostListItem key={post.id} {...post} />
))}
{promotionsQuery.data.map((promotion) => (
<Promotion key={promotion.id} {...promotion} />
))}
</Fragment>
)
}

This is why we make Suspensive.

Suspense, ClientOnly, DefaultProps

When using frameworks like Next.js, it can be difficult to use Suspense on the server.

Or, there are times when you don’t want to use Suspense on the server.

In this case, you can easily solve it by using Suspensive's ClientOnly.

Just wrap ClientOnly and it will be solved.

or Suspense in Suspense can easily handle these cases by using the clientOnly prop.

Easy, right?

However, when developing, it is sometimes difficult to add fallbacks to Suspense one by one.

Especially when working on a product like Admin, there are cases where designers do not specify each one, so you want to provide default values. In that case, try using DefaultProps.

Sometimes, instead of the default fallback, you want to give a FadeIn-like effect.

Then, how about using FadeIn?

Of course, if you want to override the default fallback, just add it.

The designer asked me to support Skeleton instead of the default Spinner in this part~! Just add it.

const Page = () => (
<Suspense fallback={<Spinner />}>
<SuspenseQuery {...notNeedSEOQueryOptions()}>
{({ data }) => <NotNeedSEO {...data} />}
</SuspenseQuery>
</Suspense>
)

ErrorBoundaryGroup, ErrorBoundary

You should use resetKeys when you want to reset the ErrorBoundary from outside its fallback.

This has the issue of requiring resetKey to be passed down for deeply nested components. Additionally, you need to create a state to pass the resetKey.

By combining the ErrorBoundary and ErrorBoundaryGroup provided by Suspensive, you can solve these issues in a very straightforward way.

Try using ErrorBoundaryGroup.

However, when using ErrorBoundary, there are times when you may want to handle only specific errors.

In such cases, try using the shouldCatch feature provided by Suspensive’s ErrorBoundary. By passing an Error Constructor to shouldCatch, you can handle only the specified errors.

Alternatively, you can exclude that specific error from being handled.

In such cases, you can handle it by passing a callback to shouldCatch.

const Page = () => {
const [resetKey, setResetKey] = useState(0)
return (
<Fragment>
<button onClick={() => setResetKey((prev) => prev + 1)}>
error reset
</button>
<ErrorBoundary resetKeys={[resetKey]} fallback="error">
<ThrowErrorComponent />
</ErrorBoundary>
<DeepComponent resetKeys={[resetKey]} />
</Fragment>
)
}
const DeepComponent = ({ resetKeys }) => (
<ErrorBoundary resetKeys={resetKeys} fallback="error">
<ThrowErrorComponent />
<ErrorBoundary resetKeys={resetKeys} fallback="error">
<ThrowErrorComponent />
</ErrorBoundary>
</ErrorBoundary>
)