<Suspense/>
, <ErrorBoundary/>
, <ErrorBoundaryGroup/>
, etc. are provided. Use them easily without any efforts.
It is simply extensions of react's concepts. Named friendly with originals like just <Suspense/>
, <ErrorBoundary/>
, <ErrorBoundaryGroup/>
.
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.
-
Using
SuspenseQuery
, you can remove depth. -
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>)