diff --git a/.changeset/little-ladybugs-relate.md b/.changeset/little-ladybugs-relate.md new file mode 100644 index 000000000..ac0e961ac --- /dev/null +++ b/.changeset/little-ladybugs-relate.md @@ -0,0 +1,5 @@ +--- +"@suspensive/react": patch +--- + +feat(react): add ErrorBoundary.Consumer, ErrorBoundaryGroup.Consumer diff --git a/README.md b/README.md index 0baa428a2..c9e8c73ce 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,9 @@ import { Suspense, ErrorBoundary, ErrorBoundaryGroup, Delay } from '@suspensive/ const Example = () => ( - } /> + + {(group) => } + ( <> diff --git a/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.en.mdx b/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.en.mdx index 081eb2eca..c13c4b195 100644 --- a/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.en.mdx +++ b/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.en.mdx @@ -4,20 +4,24 @@ import { Callout } from '@/components' This is a component for managing multiple ``s easily. -``s as children of nested `` will also be reset by parent ``. +``s as children of nested `` will also be reset by parent ``. -```tsx /ErrorBoundaryGroup/ +```jsx /ErrorBoundaryGroup/ import { ErrorBoundaryGroup, ErrorBoundary } from '@suspensive/react' const Example = () => ( {/* Resets all ErrorBoundaries that are children of ErrorBoundaryGroup. All ErrorBoundaries within nested ErrorBoundaryGroups are also reset. */} - } /> + + {(group) => } + <>{props.error}}> - } /> + + {(group) => } + <>{props.error}}> @@ -30,18 +34,22 @@ const Example = () => ( If you want to block resetting nested `` by parent ``, Use blockOutside. -```tsx /blockOutside/ +```jsx /blockOutside/ import { ErrorBoundaryGroup, ErrorBoundary } from '@suspensive/react' const Example = () => ( - } /> + + {(group) => } + <>{props.error}}> {/* blockOutside prop prevents reset by the parent ErrorBoundaryGroup*/} - } /> + + {(group) => } + <>{props.error}}> diff --git a/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.ko.mdx b/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.ko.mdx index 7c512c62d..005c0dd98 100644 --- a/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.ko.mdx +++ b/docs/suspensive.org/src/pages/docs/react/ErrorBoundaryGroup.ko.mdx @@ -4,20 +4,24 @@ import { Callout } from '@/components' 이 컴포넌트로 children인 ``들을 쉽게 관리할 수 있습니다. -중첩된 ``의 children인 ``들도 상위 ``으로도 reset됩니다. +중첩된 ``의 children인 ``들도 상위 ``으로도 reset됩니다. -```tsx /ErrorBoundaryGroup/ +```jsx /ErrorBoundaryGroup/ import { ErrorBoundaryGroup, ErrorBoundary } from '@suspensive/react' const Example = () => ( {/* ErrorBoundaryGroup의 children인 ErrorBoundary를 모두 reset합니다. 중첩된 ErrorBoundaryGroup내의 ErrorBoundary도 모두 reset합니다. */} - } /> + + {(group) => } + <>{props.error}}> - } /> + + {(group) => } + <>{props.error}}> @@ -30,18 +34,22 @@ const Example = () => ( 만약 상위 ``에 의한 하위 ``의 reset을 막고 싶다면 blockOutside을 사용하면 됩니다. -```tsx /blockOutside/ +```jsx /blockOutside/ import { ErrorBoundaryGroup, ErrorBoundary } from '@suspensive/react' const Example = () => ( - } /> + + {(group) => } + <>{props.error}}> {/* blockOutside prop으로 상위 ErrorBoundaryGroup에 의한 reset을 막습니다 */} - } /> + + {(group) => } + <>{props.error}}> diff --git a/docs/suspensive.org/src/pages/docs/react/wrap.en.mdx b/docs/suspensive.org/src/pages/docs/react/wrap.en.mdx index 0c5f8b671..c9e23daed 100644 --- a/docs/suspensive.org/src/pages/docs/react/wrap.en.mdx +++ b/docs/suspensive.org/src/pages/docs/react/wrap.en.mdx @@ -2,8 +2,8 @@ wrap was created to wrap components with Suspense, ErrorBoudnary, ErrorBoundaryGroup, etc. provided by @suspensive/react. -```tsx /wrap/ -import { wrap } from '@suspensive/react' +```jsx /wrap/ +import { wrap, ErrorBoundaryGroup } from '@suspensive/react' import { useSuspenseQuery } from '@suspensive/react-query' export const Page = wrap @@ -13,7 +13,6 @@ export const Page = wrap .on( // will make component wrapped in , and () => { - const errorBoundaryGroup = useErrorBoundaryGroup() const { data: postList } = useSuspenseQuery({ queryKey: ['posts'], queryFn: () => fetch(`https://exmaple.com/posts`).then((res) => res.json()), @@ -21,7 +20,9 @@ export const Page = wrap return ( <> - + + {(group) => } + {postList.map((post) => ( ))} @@ -30,18 +31,16 @@ export const Page = wrap } ) -const PostItem = wrap - .ErrorBoundary({ fallback: ({ error }) => <>{error.message} }) - .Suspense({ fallback: }) - .on<{ id: string }>( - // will make component have PostProps wrapped in and - (props) => { - const { data: post } = useSuspenseQuery({ - queryKey: ['posts', props.id], - queryFn: () => fetch(`https://exmaple.com/posts/${props.id}`).then((res) => res.json()), - }) +const PostItem = + wrap.ErrorBoundary({ fallback: ({ error }) => <>{error.message} }).Suspense({ fallback: }).on < + { id: string } > + // will make component have PostProps wrapped in and + ((props) => { + const { data: post } = useSuspenseQuery({ + queryKey: ['posts', props.id], + queryFn: () => fetch(`https://exmaple.com/posts/${props.id}`).then((res) => res.json()), + }) - return <>{post.title} - } - ) + return <>{post.title} + }) ``` diff --git a/docs/suspensive.org/src/pages/docs/react/wrap.ko.mdx b/docs/suspensive.org/src/pages/docs/react/wrap.ko.mdx index d645002f3..7bbea9b53 100644 --- a/docs/suspensive.org/src/pages/docs/react/wrap.ko.mdx +++ b/docs/suspensive.org/src/pages/docs/react/wrap.ko.mdx @@ -2,8 +2,8 @@ wrap은 @suspensive/react에서 제공하는 Suspense, ErrorBoudnary, ErrorBoundaryGroup 등으로 컴포넌트를 감싸기 위해 만들어졌습니다. -```tsx /wrap/ -import { wrap } from '@suspensive/react' +```jsx /wrap/ +import { wrap, ErrorBoundaryGroup } from '@suspensive/react' import { useSuspenseQuery } from '@suspensive/react-query' export const Page = wrap @@ -13,7 +13,6 @@ export const Page = wrap .on( // , , 에 감싸진 컴포넌트를 만듭니다. () => { - const errorBoundaryGroup = useErrorBoundaryGroup() const { data: postList } = useSuspenseQuery({ queryKey: ['posts'], queryFn: () => fetch(`https://exmaple.com/posts`).then((res) => res.json()), @@ -21,7 +20,9 @@ export const Page = wrap return ( <> - + + {(group) => } + {postList.map((post) => ( ))} @@ -30,18 +31,16 @@ export const Page = wrap } ) -const PostItem = wrap - .ErrorBoundary({ fallback: ({ error }) => <>{error.message} }) - .Suspense({ fallback: }) - .on<{ id: string }>( - // , 에 감싸진 컴포넌트를 만듭니다. - (props) => { - const { data: post } = useSuspenseQuery({ - queryKey: ['posts', props.id], - queryFn: () => fetch(`https://exmaple.com/posts/${props.id}`).then((res) => res.json()), - }) +const PostItem = + wrap.ErrorBoundary({ fallback: ({ error }) => <>{error.message} }).Suspense({ fallback: }).on < + { id: string } > + // , 에 감싸진 컴포넌트를 만듭니다. + ((props) => { + const { data: post } = useSuspenseQuery({ + queryKey: ['posts', props.id], + queryFn: () => fetch(`https://exmaple.com/posts/${props.id}`).then((res) => res.json()), + }) - return <>{post.title} - } - ) + return <>{post.title} + }) ``` diff --git a/packages/react/src/ErrorBoundary.tsx b/packages/react/src/ErrorBoundary.tsx index c20d63080..a53a433b7 100644 --- a/packages/react/src/ErrorBoundary.tsx +++ b/packages/react/src/ErrorBoundary.tsx @@ -144,32 +144,42 @@ class BaseErrorBoundary extends Component( - ({ devMode, fallback, children, onError, onReset, resetKeys, ...props }, ref) => { - const group = useContext(ErrorBoundaryGroupContext) ?? { resetKey: 0 } - const baseErrorBoundaryRef = useRef(null) - useImperativeHandle(ref, () => ({ - reset: () => baseErrorBoundaryRef.current?.reset(), - })) +export const ErrorBoundary = Object.assign( + (() => { + const ErrorBoundary = forwardRef<{ reset(): void }, ErrorBoundaryProps>( + ({ devMode, fallback, children, onError, onReset, resetKeys, ...props }, ref) => { + const group = useContext(ErrorBoundaryGroupContext) ?? { resetKey: 0 } + const baseErrorBoundaryRef = useRef(null) + useImperativeHandle(ref, () => ({ + reset: () => baseErrorBoundaryRef.current?.reset(), + })) - return ( - - {children} - {process.env.NODE_ENV !== 'production' && devMode && } - + return ( + + {children} + {process.env.NODE_ENV !== 'production' && devMode && } + + ) + } ) + if (process.env.NODE_ENV !== 'production') { + ErrorBoundary.displayName = 'ErrorBoundary' + } + + return ErrorBoundary + })(), + { + Consumer: ({ children }: { children: (errorBoundary: ReturnType) => ReactNode }) => + children(useErrorBoundary()), } ) -if (process.env.NODE_ENV !== 'production') { - ErrorBoundary.displayName = 'ErrorBoundary' -} const ErrorBoundaryContext = createContext<({ reset: () => void } & ErrorBoundaryState) | null>(null) if (process.env.NODE_ENV !== 'production') { diff --git a/packages/react/src/ErrorBoundaryGroup.spec.tsx b/packages/react/src/ErrorBoundaryGroup.spec.tsx index c9b1a454d..37b8b8413 100644 --- a/packages/react/src/ErrorBoundaryGroup.spec.tsx +++ b/packages/react/src/ErrorBoundaryGroup.spec.tsx @@ -14,7 +14,9 @@ describe('', () => { it('should reset all ErrorBoundaries in children', async () => { render( - } /> + + {(group) => } + {Array.from({ length: innerErrorBoundaryCount }).map((_, key) => (
{props.error.message}
}> @@ -37,7 +39,9 @@ describe('', () => { it('should reset all ErrorBoundaries in children even if it is nested, but if use blockOutside, can block reset by outside', async () => { render( - } /> + + {(group) => } + {Array.from({ length: innerErrorBoundaryCount }).map((_, index) => (
{props.error.message}
}> diff --git a/packages/react/src/ErrorBoundaryGroup.tsx b/packages/react/src/ErrorBoundaryGroup.tsx index e0b51c6b9..902edf180 100644 --- a/packages/react/src/ErrorBoundaryGroup.tsx +++ b/packages/react/src/ErrorBoundaryGroup.tsx @@ -28,34 +28,48 @@ export interface ErrorBoundaryGroupProps extends PropsWithChildren { * ErrorBoundaryGroup is Component to manage multiple ErrorBoundaries * @see {@link https://suspensive.org/docs/react/ErrorBoundaryGroup} */ -export const ErrorBoundaryGroup = ({ blockOutside = false, children }: ErrorBoundaryGroupProps) => { - const [resetKey, reset] = useReducer(increase, 0) - const parentGroup = useContext(ErrorBoundaryGroupContext) - const isParentGroupResetKeyChanged = useIsChanged(parentGroup?.resetKey) +export const ErrorBoundaryGroup = Object.assign( + (() => { + const ErrorBoundaryGroup = ({ blockOutside = false, children }: ErrorBoundaryGroupProps) => { + const [resetKey, reset] = useReducer(increase, 0) + const parentGroup = useContext(ErrorBoundaryGroupContext) + const isParentGroupResetKeyChanged = useIsChanged(parentGroup?.resetKey) - useEffect(() => { - if (!blockOutside && isParentGroupResetKeyChanged) { - reset() - } - }, [isParentGroupResetKeyChanged, blockOutside]) + useEffect(() => { + if (!blockOutside && isParentGroupResetKeyChanged) { + reset() + } + }, [isParentGroupResetKeyChanged, blockOutside]) - const value = useMemo(() => ({ reset, resetKey }), [resetKey]) + const value = useMemo(() => ({ reset, resetKey }), [resetKey]) - return {children} -} -if (process.env.NODE_ENV !== 'production') { - ErrorBoundaryGroup.displayName = 'ErrorBoundaryGroup' -} + return {children} + } + if (process.env.NODE_ENV !== 'production') { + ErrorBoundaryGroup.displayName = 'ErrorBoundaryGroup' + } -const ErrorBoundaryGroupReset = ({ - trigger, -}: { - /** - * When you want to reset multiple ErrorBoundaries as children of ErrorBoundaryGroup, You can combine any other components with this trigger's reset - */ - trigger: (errorBoundaryGroup: ReturnType) => ReactNode -}) => trigger(useErrorBoundaryGroup()) -ErrorBoundaryGroup.Reset = ErrorBoundaryGroupReset + return ErrorBoundaryGroup + })(), + { + /** + * @deprecated Use ErrorBoundaryGroup.Consumer + */ + Reset: ({ + trigger, + }: { + /** + * When you want to reset multiple ErrorBoundaries as children of ErrorBoundaryGroup, You can combine any other components with this trigger's reset + */ + trigger: (errorBoundaryGroup: ReturnType) => ReactNode + }) => trigger(useErrorBoundaryGroup()), + Consumer: ({ + children, + }: { + children: (errorBoundaryGroup: ReturnType) => ReactNode + }) => children(useErrorBoundaryGroup()), + } +) export const useErrorBoundaryGroup = () => { const group = useContext(ErrorBoundaryGroupContext) diff --git a/websites/visualization/src/app/react-query/page.tsx b/websites/visualization/src/app/react-query/page.tsx index 508a0b3ea..0fb9866eb 100644 --- a/websites/visualization/src/app/react-query/page.tsx +++ b/websites/visualization/src/app/react-query/page.tsx @@ -13,7 +13,7 @@ export default wrap.ErrorBoundaryGroup({}).on(function Page() { return (
- } /> + {(group) => }
diff --git a/websites/visualization/src/app/react/page.tsx b/websites/visualization/src/app/react/page.tsx index beba6d29a..0b67a5c13 100644 --- a/websites/visualization/src/app/react/page.tsx +++ b/websites/visualization/src/app/react/page.tsx @@ -13,12 +13,16 @@ export default function Page() {
- } /> + + {(group) => } +
- } /> + + {(group) => } +
}> @@ -42,7 +46,9 @@ export default function Page() {
- } /> + + {(group) => } +
}>