diff --git a/docs/suspensive.org/src/content/en/docs/react/migrate-to-v3.mdx b/docs/suspensive.org/src/content/en/docs/react/migrate-to-v3.mdx index 666e493ac..843089c11 100644 --- a/docs/suspensive.org/src/content/en/docs/react/migrate-to-v3.mdx +++ b/docs/suspensive.org/src/content/en/docs/react/migrate-to-v3.mdx @@ -1,9 +1,106 @@ +import { Callout } from '@/components' + # Migrating to v3 ## What's new -### Draft +### Now thrown error in `ErrorBoundary` fallback will be passed to parent [#1409](https://github.com/toss/suspensive/pull/1409) + +It is not the developer's intention to expose fallbacks recursively due to fallback errors, in v3, errors thrown from fallbacks are caught by the parent ErrorBoundary. +So this is a new mental model and a BREAKING CHANGE, please understand and use the new behavior. + + + +Errors thrown from the fallback of AS-IS v2 ErrorBoundary cannot be handled by the parent ErrorBoundary, so they are caught by themselves and fallbacks are exposed recursively. + +> This is also the case in [react-error-boundary](https://github.com/bvaughn/react-error-boundary) where fallbacks are exposed recursively as follows. + +1. children is exposed +2. fallback is exposed +3. fallback is exposed +4. fallback is exposed +5. ... recursively exposed fallback is exposed + + + +Now, errors thrown from the fallback of ErrorBoundary are caught by the parent ErrorBoundary. Therefore, the behavior is as follows. + +1. children are exposed +2. fallback is exposed +3. This is expected + +```tsx /This is expected/ /fallback is exposed/ /children is exposed/ +const Example = () => ( + <>This is expected}> + ( + + fallback is exposed + + )} + > + + children is exposed + + + +) + +const Throw = { + Error: ({ + message, + after = 0, + children, + }: PropsWithChildren<{ message: string; after?: number }>) => { + const [isNeedThrow, setIsNeedThrow] = useState(after === 0) + if (isNeedThrow) { + throw new Error(message) + } + useTimeout(() => setIsNeedThrow(true), after) + return <>{children} + }, +} +``` ## Handling BREAKING CHANGES -### Draft +### Remove `wrap` & Add `with` + +`wrap` has been removed in v3. We added a `with` method to each component that can replace the functionality of wrap. + +1. You don't need to understand the builder pattern used in wrap. +2. Since wrap includes all components internally, the build size increases. In v3, you can import and use only the components you need. + +```diff ++ import { ErrorBoundaryGroup, ErrorBoundary, Suspense } from '@suspensive/react' +- import { wrap } from '@suspensive/react' + import { useSuspenseQuery } from '@suspensive/react-query' + ++ const Example = ErrorBoundaryGroup.with( ++ { blockOutside: false }, ++ ErrorBoundary.with( ++ { fallback: ({ error }) => <>{error.message}, onError: logger.log }, ++ Suspense.with({ fallback: <>loading..., clientOnly: true }, () => { ++ const query = useSuspenseQuery({ ++ queryKey: ['key'], ++ queryFn: () => api.text(), ++ }) ++ return <>{query.data.text} ++ }) ++ ) ++ ) +- 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} +- }) +``` diff --git a/docs/suspensive.org/src/content/ko/docs/react/migrate-to-v3.mdx b/docs/suspensive.org/src/content/ko/docs/react/migrate-to-v3.mdx index 3e0917b6c..647d021c2 100644 --- a/docs/suspensive.org/src/content/ko/docs/react/migrate-to-v3.mdx +++ b/docs/suspensive.org/src/content/ko/docs/react/migrate-to-v3.mdx @@ -1,9 +1,106 @@ +import { Callout } from '@/components' + # v3로 마이그레이션하기 ## 새로운 기능 -### Draft +### 이제 `ErrorBoundary` fallback에서 thrown error 발생시 부모로 전달됩니다 [#1409](https://github.com/toss/suspensive/pull/1409) + +fallback의 에러에 의해 fallback을 재귀적으로 노출하는 것은 개발자가 의도한 것이 아니기 때문에 이를 방지하기 위해 v3에서는 fallback에서 throw된 에러를 부모 ErrorBoundary에 의해 잡히도록 변경되었습니다. +따라서 이것은 새로운 멘탈모델이자 BREAKING CHANGE이므로 새 동작방식을 이해하고 사용하시기 바랍니다. + + + +AS-IS v2 ErrorBoundary의 fallback에서 throw된 error는 부모 ErrorBoundary에서 처리할 수 없고 스스로 catch하고 폴백을 재귀적으로 노출되었었습니다 + +> 이는 [react-error-boundary](https://github.com/bvaughn/react-error-boundary)에서도 아래와 같이 fallback이 재귀적으로 노출되도록 동작합니다. + +1. children is exposed +2. fallback is exposed +3. fallback is exposed +4. fallback is exposed +5. ... 재귀적으로 fallback is exposed을 노출했습니다 + + + +`ErrorBoundary`의 fallback에서 throw된 error는 부모 ErrorBoundary에 의해 잡힙니다. 따라서 아래처럼 동작하게 됩니다. + +1. children is exposed +2. fallback is exposed +3. This is expected + +```tsx /This is expected/ /fallback is exposed/ /children is exposed/ +const Example = () => ( + <>This is expected}> + ( + + fallback is exposed + + )} + > + + children is exposed + + + +) + +const Throw = { + Error: ({ + message, + after = 0, + children, + }: PropsWithChildren<{ message: string; after?: number }>) => { + const [isNeedThrow, setIsNeedThrow] = useState(after === 0) + if (isNeedThrow) { + throw new Error(message) + } + useTimeout(() => setIsNeedThrow(true), after) + return <>{children} + }, +} +``` ## BREAKING CHANGES 처리하기 -### Draft +### `wrap` 제거 & `with` 추가 + +v3에서 `wrap`을 제거했습니다. wrap의 기능을 대체할 수 있는 `with` 메소드를 각 컴포넌트에 모두 추가해두었습니다. + +1. wrap에서 사용하는 builder 패턴을 이해하지 않아도 됩니다. +2. wrap은 내부적으로 모든 컴포넌트를 포함하고 있기 때문에 빌드 사이즈가 커집니다. v3에서는 필요한 컴포넌트만 import하여 사용할 수 있습니다. + +```diff ++ import { ErrorBoundaryGroup, ErrorBoundary, Suspense } from '@suspensive/react' +- import { wrap } from '@suspensive/react' + import { useSuspenseQuery } from '@suspensive/react-query' + ++ const Example = ErrorBoundaryGroup.with( ++ { blockOutside: false }, ++ ErrorBoundary.with( ++ { fallback: ({ error }) => <>{error.message}, onError: logger.log }, ++ Suspense.with({ fallback: <>loading..., clientOnly: true }, () => { ++ const query = useSuspenseQuery({ ++ queryKey: ['key'], ++ queryFn: () => api.text(), ++ }) ++ return <>{query.data.text} ++ }) ++ ) ++ ) +- 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} +- }) +```