<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
ErrorBoundary의 fallback 외부에서 ErrorBoundary를 reset하고 싶을 때 resetKeys를 사용해야 합니다.
이는 깊은 컴포넌트의 경우 resetKey를 전달해야 하는 문제가 있습니다. 또한 state를 만들어 resetKey를 전달해야 하는 문제가 있습니다.
Suspensive가 제공하는 ErrorBoundary와 ErrorBoundaryGroup을 조합하면 이러한 문제를 매우 단순히 해결할 수 있습니다.
ErrorBoundaryGroup을 사용해보세요.
그런데 ErrorBoundary를 사용하다보면 특정 Error에 대해서만 처리하고 싶을 때가 있습니다.
그럴 때에는 Suspensive가 제공하는 ErrorBoundary의 shouldCatch를 써보세요. 이 shouldCatch에 Error Constructor를 넣으면 해당 Error에 대해서만 처리할 수 있습니다.
혹은 반대로 그 Error만 빼고 처리할 수도 있습니다.
그럴 때에는 shouldCatch에 callback을 넣어서 처리할 수 있습니다.
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>)