-
Notifications
You must be signed in to change notification settings - Fork 301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(error-boundary): add ErrorBoundaryGroup #157
Conversation
👷 Deploy request for slash-libraries pending review.Visit the deploys page to approve it
|
3d6cc3e
to
269353e
Compare
9ee0a70
to
ba03ae8
Compare
Thanks for your pull request, @manudeli. Now I see the problem. However I see some problems as below:
|
@@ -1,3 +1,4 @@ | |||
/** @tossdocs-ignore */ | |||
export { default as ErrorBoundary } from './ErrorBoundary'; | |||
export { ResetKey, useResetKey, withResetKey } from './contexts/ResetKey'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted in the earlier comment, I think
ErrorBoundaryContext
&withErrorBoundaryContext
ErrorBoundaryGroup
&withErrorBoundaryGroup
might be a better naming.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of useResetKey
, what about useErrorBoundaryContext
?
const context = useErrorBoundaryContext();
context.reset();
I think resetKey
is implementation detail and I would like to hide it from the public API.
ErrorBoundaryGroupI changed ResetKey into ErrorBoundaryGroup If ErrorBoundaryGroup as parent of ErrorBoundarynow, default ErrorBoundary can be reset by ErrorBoundaryGroup's groupResetKey of ErrorBoundaryGroup context in background. also, I named trigger resetting ErrorBoundaryGroup as ErrorBoundaryGroup.Reset that can combine with every component a feature resetting all ErrorBoundary as ErrorBoundaryGroup's children. const Example = () => (
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Reset trigger={({ resetGroup }) => <button onClick={resetGroup}>Reset All</button>} />
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
</ErrorBoundaryGroup>
); Edge casesIf No ErrorBoundaryGroup as parentbut, default ErrorBoundary without ErrorBoundaryGroup also work self const Example = () => (
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>) Multiple nested ErrorBoundaryGroupIf reset most top level parent, Every ErrorBoundaryGroup will be reset. const Example = () => (
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Reset trigger={({ resetGroup }) => <button onClick={resetGroup}>Reset All</button>} />
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Reset trigger={({ resetGroup }) => <button onClick={resetGroup}>Reset only inside nested ErrorBoundaryGroup</button>} />
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
</ErrorBoundaryGroup>
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
</ErrorBoundaryGroup>
); but If turn on blockOutside prop of ErrorBoundaryGroup, It will block parent's resetting to children of itself const Example = () => (
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Reset trigger={({ resetGroup }) => <button onClick={resetGroup}>Reset All</button>} />
<ErrorBoundaryGroup blockOutside>{/*use blockOutside to block resetting by outside of ErrorBoundaryGroup.Reset*/}
<ErrorBoundaryGroup.Reset trigger={({ resetGroup }) => <button onClick={resetGroup}>Reset only inside nested ErrorBoundaryGroup</button>} />
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
</ErrorBoundaryGroup>
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
<ErrorBoundary
renderFallback={({ reset, error }) => <button onClick={reset}>reset {JSON.stringify(error)}</button>}
>
<ThrowComponent />
</ErrorBoundary>
</ErrorBoundaryGroup>
); Comment Reply
Cool, I renamed ResetKey into ErrorBoundaryGroup after your comment.
I worried that changing original implementation can be rude for you. but If you accept adding new feature of original ErrorBoundary, why not. so I add new feature of ErrorBoundary can be reset by ErrorBoundaryGroup's groupResetKey, If there is no ErrorBoundaryGroup as parent, it won't work. and I removed ErrorBoundary.ResetKey and BaseErrorBoundary 👍
After your checking this implementation and accept all interfaces (welcome your changing too), I will try to add test code. Actually I have very small experience to add test code. but I want to try it. Allow your patience to wait me please :) |
interface ResetRef { | ||
reset?(): void; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I delete unnecessary ResetRef. I think this interface need to be driven from ErrorBoundary.
const ErrorBoundary = forwardRef<{ reset?(): void }, ComponentProps<typeof BaseErrorBoundary>>((props, resetRef) => { | ||
const { groupResetKey } = useErrorBoundaryGroup(); | ||
const resetKeys = groupResetKey ? [groupResetKey, ...(props.resetKeys || [])] : props.resetKeys; | ||
|
||
const ref = useRef<BaseErrorBoundary | null>(null); | ||
useImperativeHandle(resetRef, () => ({ | ||
reset: () => ref.current?.resetErrorBoundary(), | ||
})); | ||
|
||
return <BaseErrorBoundary {...props} resetKeys={resetKeys} />; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added new feature using ErrorBoundaryGroup's groupResetKey.
const ErrorBoundaryGroupReset = ({ trigger }: { trigger: ComponentType<{ resetGroup: () => void }> }) => { | ||
const { resetGroup } = useErrorBoundaryGroup(); | ||
const Trigger = trigger; | ||
|
||
return <Trigger resetGroup={resetGroup} />; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hided groupResetKey. because default ErrorBoundary can use it defaultly now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks.
So cool, take your time on tests. Let me know when tests are done. (If it is too hard, don't hesitate to ask us!) |
@raon0211 by the way, During trying to add test code, I encounter this issue before trying adding test code, I solved this problem in only this package(@toss/error-boundary) in this commit, but it's just my way and this can make difference with build convention of @toss/slash's all packages. If adding test code is not scope of this PR, I'll delete it. check it please. |
Okay, please let us some time. We plan to add one in this week. |
I added some tests here |
@@ -95,3 +106,17 @@ export default class ErrorBoundary extends Component<PropsWithRef<PropsWithChild | |||
return children; | |||
} | |||
} | |||
|
|||
const ErrorBoundary = forwardRef<{ reset?(): void }, ComponentProps<typeof BaseErrorBoundary>>((props, resetRef) => { | |||
const { groupResetKey } = useErrorBoundaryGroup(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would exposing groupResetKey
be necessary? While writing some tests, I thought only exposing the reset
function might be nice.
const reset = useResetErrorBoundaryGroup();
// or...
const group = useErrorBoundaryGroup();
group.reset(); // The implementation detail `groupResetKey` is private, it only exposes `reset`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks👍 I removed groupResetKey and renamed resetGroup as reset like your comment in df96812
</ErrorBoundaryGroupContext.Provider> | ||
); | ||
}; | ||
ErrorBoundaryGroup.Reset = ErrorBoundaryGroupReset; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the use case for ErrorBoundaryGroup.Reset
? Wouldn't exposing useErrorBoundaryGroup
sufficient? 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought developer that have to use class components may want this ErrorBoundaryGroup.Reset.
also, HOC using useErrorBoundaryGroup hook can be another option to do it. but I thought providing ErrorBoundaryGroup.Reset can be easiest way to reset ErrorBoundaryGroup.
If we provide ErrorBoundaryGroup.Reset, developer don't have to import useErrorBoundaryGroup. because ErrorBoundaryGroup.Reset is compounded for it.
I want to contain meaning of resetting group into ErrorBoundaryGroup more definitely by using compound component pattern
import { ErrorBoundaryGroup } from '@toss/error-boundary'
// If use ErrorBoundaryGroup.Reset, developer don't have to import another thing to reset.
<ErrorBoundaryGroup>
<ErrorBoundaryGroup.Reset trigger={({ reset }) => <ClassComponent onClick={reset} />}>
<ErrorBoundary>
<Children />
</ErrorBoundary>
</ErrorBoundaryGroup>
but, I think it's up to you. both implementation are good for me! choose it freely please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If implementing the component is easy and straightforward, I prefer to provide smaller sets of API, since it would improve our maintainability. Users of our library could also benefit in increased flexibility (they could decide how to design ErrorBoundary.Reset
's API.) What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understood your intention. I removed ErrorBoundary.Reset in b12ec9c
Sorry for adding the tests late. It was a hard time for me last week, and I was so busy. Thanks for being patient 🙇 |
396acb7
to
9a15657
Compare
9a15657
to
df96812
Compare
Oh,, I mistook it (The reset function was not being called). Thanks for your eagle eyes 👀 and fixing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great work. Thanks, @manudeli !
Overview
Referencing Issue
#144
I Added ErrorBoundary.ResetKey, ResetKey, useResetKey, withResetKey to reset ErrorBoundary outside of itself easily.
Problem
Resetting ErrorBoundary outside of itself is tiresome
Solution
if use useResetKey or ErrorBoundary.ResetKey, prop drilling is not necessary.
How to use
Using withResetKey
Nested Component using useResetKey
Using ResetKey.Provider, ResetKey.Consumer
or Using ResetKey.Provider, ErrorBoundary.ResetKey
PR Checklist