Migrating to v2
What's new
New <DevMode/>
#470 (opens in a new tab)
This is our new feature for DX with new devMode
prop of <Suspense/>
, <ErrorBoundary/>
We have seen many people use way such as forcing a fallback into each component to test the fallback directly or reloading the page to re-fetch. so We added the devMode
prop to each <Suspense/>
and <ErrorBoundary/>
to easily test the fallback of them.
import { useState } from 'react'
import { DevMode, Suspensive, SuspensiveProvider, Suspense, ErrorBoundary } from '@suspensive/react'
const Example = () => {
const [suspensive] = useState(new Suspensive())
return (
<SuspensiveProvider value={suspensive}>
{/* This devMode prop will work only in development mode */}
<Suspense fallback={<>loading...</>} devMode={{ showFallback: true }}>
{/* children */}
</Suspense>
<ErrorBoundary fallback={<>error</>} devMode={{ showFallback: true }}>
{/* children */}
</ErrorBoundary>
{/* This <DevMode/> component will appear only in development mode */}
{/* If developer click <DevMode/>, devMode prop of <Suspense/> <ErrorBoundary/> will be activated */}
<DevMode />
</SuspensiveProvider>
)
}
New wrap
builder #270 (opens in a new tab)
A new feature that wraps a component in <Suspense/>
, <ErrorBoundary/>
, and <ErrorBoundaryGroup/>
all at once.
For <Suspense/>
, <ErrorBoundary/>
, <ErrorBoundaryGroup/>
, etc. many people use hoc to wrap these around a component. This is because these components require some processing on their children. So, in order to not unnecessarily divide components and create depth, we use the hocs for each interface, withErrorBoundary, withErrorBoundaryGroup, and withSuspense, but as we often use each hoc in combination, we also need to improve readability. To improve this, we decided to provide wrap.
import { wrap } from '@suspensive/react'
import { useSuspenseQuery } from '@suspensive/react-query'
const Example = wrap
.ErrorBoundaryGroup({ blockOutside: false })
.ErrorBoundary({ fallback: ({ error }) => <>{error.message}</>, onError: logger.log })
.Suspense({ fallback: <>loading...</>, clientOnly: true })
.on(() => {
const query = useSuspenseQuery({
queryKey: ['key'],
queryFn: () => api.text(),
})
return <>{query.data.text}</>
})
New shouldCatch
prop of <ErrorBoundary/>
#569 (opens in a new tab)
Suspensive's <ErrorBoundary/>
can catch all thrown errors that occur in children. However, because it catches all thrown errors, when using <ErrorBoundary/>
, I thought about placing <ErrorBoundary/>
in a narrower position.
For this reason, we added a new prop called shouldCatch to ErrorBoundary, which allows you to set which Error should be caught.
shouldCatch
: ErrorConstructor
import { ErrorBoundary } from '@suspensive/react'
class CustomError extends Error {}
const Example = () => {
return (
<ErrorBoundary fallback={({ error }) => <>RestError: {error.message}</>}>
<ErrorBoundary
shouldCatch={CustomError}
onError={logOnCustomError}
fallback={({ error }) => <>CustomError: {error.message}</>}
>
<ThrowErrorComponent />
</ErrorBoundary>
</ErrorBoundary>
)
}
shouldCatch
: callback
import { ErrorBoundary } from '@suspensive/react'
class CustomError extends Error {}
const Example = () => {
return (
<ErrorBoundary fallback={({ error }) => <>RestError: {error.message}</>}>
<ErrorBoundary
shouldCatch={(error) => error instanceof CustomError}
onError={logOnCustomError}
fallback={({ error }) => <>CustomError: {error.message}</>}
>
<ThrowErrorComponent />
</ErrorBoundary>
</ErrorBoundary>
)
}
shouldCatch
: boolean
import { ErrorBoundary } from '@suspensive/react'
class CustomError extends Error {}
const Example = () => {
return (
<ErrorBoundary fallback={({ error }) => <>RestError: {error.message}</>}>
<ErrorBoundary
shouldCatch={new Date().toISOString() > '2024-01-01T00:00:00.000Z'}
onError={logOnErrorAfter2024}
fallback={({ error }) => <>ErrorAfter2024: {error.message}</>}
>
<ThrowErrorComponent />
</ErrorBoundary>
</ErrorBoundary>
)
}
New <ErrorBoundary.Consumer/>
, <ErrorBoundaryGroup.Consumer/>
#610 (opens in a new tab)
These components can be used to use useErrorBoundary
, useErrorBoundaryGroup
in jsx inlinely
import { ErrorBoundary, ErrorBoundaryGroup } from '@suspensive/react'
const Example = () => {
return (
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Consumer>
{({ reset }) => <button onClick={reset}>reset all</button>}
</ErrorBoundaryGroup.Consumer>
<ErrorBoundary fallback={({ error }) => <>{error.message}</>}>
<ErrorBoundary.Consumer>
{({ setError }) => <button onClick={() => setError(new Error('error message'))}>setError</button>}
</ErrorBoundary.Consumer>
</ErrorBoundary>
</ErrorBoundaryGroup>
)
}
Handling BREAKING CHANGES
Removed <AsyncBoundary/>
We removed <AsyncBoundary/>
in v2 #295 (opens in a new tab)
Because <AsyncBoundary/>
uses <ErrorBoundary/>
internally, it can be used with useErrorBoundary
and is affected by <ErrorBoundaryGroup/>
. We decided to remove this component from v2, believing that it would be better for maintainability and interface unification for library users.
<AsyncBoundary/>
's feature is just wrap two component(<Suspense/>
, <Errorboundry/>
) by one.
So, you can split by two like this.
+ import { Suspense, Errorboundry } from '@suspensive/react'
- import { AsyncBoundary } from '@suspensive/react'
+ <Errorboundry fallback={<Error />} onError={onError} onReset={onReset}>
+ <Suspense fallback={<Loading />}>
+ <Children />
+ </Suspense>
+ </Errorboundry>
- <AsyncBoundary pendingFallback={<Loading />} rejectedFallback={<Error />} onError={onError} onReset={onReset}>
- <Children />
- </AsyncBoundary>
Removed withSuspense
, withDelay
, withErrorboundry
, withErrorBoundaryGroup
These all hocs can be replaced beautifully by new hoc builder wrap
in v2
+ import { wrap } from '@suspensive/react'
- import { withSuspense, withErrorBoundary, withErrorBoundaryGroup } from '@suspensive/react'
+ const Example = wrap
+ .ErrorBoundaryGroup({ blockOutside: false })
+ .ErrorBoundary({ fallback: ({ error }) => <>{error.message}</>, onError: logger.log })
+ .Suspense({ fallback: <>loading...</>, clientOnly: true })
+ .on(() => {
+ const query = useSuspenseQuery({
+ queryKey: ['key'],
+ queryFn: () => api.text(),
+ })
+ return <>{query.data.text}</>
+ })
- const Example = withErrorBoundaryGroup(
- withErrorBoundary(
- withSuspense(
- () => {
- const query = useSuspenseQuery({
- queryKey: ['key'],
- queryFn: () => api.text(),
- })
- return <>{query.data.text}</>
- },
- { fallback: <>loading...</>, clientOnly: true }
- ),
- { fallback: ({ error }) => <>{error.message}</>, onError: logger.log }
- ),
- { blockOutside: false }
- )
+ import { wrap } from '@suspensive/react'
- import { withSuspense } from '@suspensive/react'
+ const Example = wrap
+ .Suspense({
+ fallback: <>loading...</>,
+ clientOnly: true,
+ })
+ .on(() => {
+ const query = useSuspenseQuery({
+ queryKey: ['key'],
+ queryFn: () => api.text(),
+ })
+ return <>{query.data.text}</>
+ })
- const Example = withSuspense(
- () => {
- const query = useSuspenseQuery({
- queryKey: ['key'],
- queryFn: () => api.text(),
- })
- return <>{query.data.text}</>
- },
- {
- fallback: <>loading...</>,
- clientOnly: true,
- }
- )
Removed <ErrorBoundaryGroup.Reset/>
<ErrorBoundaryGroup.Reset/>
just use useErrorBoundaryGroup
internally. so We thought that changing it to something like Context.Consumer would make the component's behavior easier to understand for React developers. We changed the name to <ErrorBoundaryGroup.Consumer/>
and kept the interface the same.
import { ErrorBoundaryGroup } from '@suspensive/react'
const Example = () => {
return (
<ErrorBoundaryGroup>
- <ErrorBoundaryGroup.Reset trigger={(group) => <button onClick={group.reset}>reset all</button>} />
+ <ErrorBoundaryGroup.Consumer>
+ {(group) => <button onClick={group.reset}>reset all</button>}
+ </ErrorBoundaryGroup.Consumer>
</ErrorBoundaryGroup>
)
}
Rename defaultOptions
→ defaultProps
of Suspensive
import { ErrorBoundaryGroup } from '@suspensive/react'
const suspensive = new Suspensive({
- defaultOptions: {
+ defaultProps: {
suspense: {
fallback: 'default loading...',
},
},
})
Rename <Suspense.CSROnly/>
→ <Suspense clientOnly/>
as prop
import { Suspense } from '@suspensive/react'
const Example = () => {
return (
- <Suspense.CSROnly fallback={<>loading...</>}>
+ <Suspense clientOnly fallback={<>loading...</>}>
<>children</>
</Suspense.CSROnly>
)
}