-
Notifications
You must be signed in to change notification settings - Fork 7
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: allow direct access to caught error and errorInfo
#9
Conversation
Thanks for opening this @davwheat. This is indeed an oversight -- I think it would be more useful to update the source code to set the actual caught error, rather than a boolean. I would be happy to accept a PR for that change. Otherwise, I'll patch and publish this evening. |
That sounds more reasonable, but I would worry about the behaviour and effects this could have if the code, for whatever reason, threw Ideally, it could return an object, or the hook could return a third array element in order to not be a breaking change? |
It is a breaking change, but we can reflect that in the package versioning and bump to 2.0.0. If any users file a request for throwing |
The reason I mentioned it was that some of my (old and terrible) code does throw falsey values, using TS enums as error codes. In that scenario, anything throwing Looking at the code in more detail, I propose that What do you think of this? Example: function MyComponent() {
const [errorData, resetError] = useErrorBoundary();
// undefined, or object containing error data
if (errorData) {
const { error, errorInfo } = errorData;
return <p>{error.message}</p>;
}
return null;
} |
Codecov Report
@@ Coverage Diff @@
## main #9 +/- ##
============================================
- Coverage 100.00% 80.00% -20.00%
============================================
Files 1 1
Lines 27 35 +8
Branches 0 1 +1
============================================
+ Hits 27 28 +1
- Misses 0 7 +7
Continue to review full report at Codecov.
|
I think we should update the callback argument to include const [error, resetError] = useErrorBoundary(
// You can also log the error to an error reporting service
(error, errorInfo) => logErrorToMyService(error, errorInfo)
); I'm curious if I like the ergonomics of having direct access to the error that was thrown, which I suspect is the dominant use case. How burdensome would updating your existing callsites to |
react-use-error-boundary/src/index.tsx Lines 67 to 70 in 74d81ec
I'll push the new code I have based on my previous comment, and let you see what you think. I do think that the same values should be available through the hook as are available through the onError callback.
It wouldn't be horrible, but I do still think that a simple falsey check looks cleaner, in my opinion. |
src/index.tsx
Outdated
interface ErrorBoundaryProps { | ||
type ErrorType = ErrorData | undefined; | ||
|
||
interface ErrorData { | ||
error: unknown; |
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 chose unknown
here as we can't guarantee the thrown value will be an instance of Error
, and I think enforcing type safety is more important than convenience, if this is meant to be a back-up/fail-safe for other errors.
Using instanceof Error
would be enough to please Typescript into allowing access to Error
's attributes.
error
is not a caught error, but a booleanerrorInfo
I'd like to hold off on adding With that, I'm still inclined to update the source code to set the caught error, rather than a boolean. I could see using eg
|
@@ -86,15 +129,15 @@ export function withErrorBoundary<Props = Record<string, unknown>>( | |||
); | |||
} | |||
|
|||
type UseErrorBoundaryReturn = [error: boolean, resetError: () => void]; | |||
type UseErrorBoundaryReturn = [hasErrored: ErrorType, resetError: () => 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.
type UseErrorBoundaryReturn = [hasErrored: ErrorType, resetError: () => void]; | |
type UseErrorBoundaryReturn = [error: Error, resetError: () => void]; |
onError={(...args) => { | ||
setError(true); | ||
componentDidCatch.current?.(...args); | ||
onError={(error: Error | WrappedError, errorInfo) => { |
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.
onError={(error: Error | WrappedError, errorInfo) => { | |
onError={(error: unknown, errorInfo) => { |
Could you add the type annotation for errorInfo
?
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 already covered in ErrorBoundaryProps
at the top.
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.
The type here should be unknown right?
setError: noop, | ||
}); | ||
|
||
export const ErrorBoundaryContext: FC = ({ children }) => { | ||
const [error, setError] = useState(false); | ||
const [error, setError] = useState<ErrorType>(undefined); |
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.
const [error, setError] = useState<ErrorType>(undefined); | |
const [error, setError] = useState<Error>(undefined); |
error: ErrorType; | ||
setError: (error: ErrorType) => 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.
error: ErrorType; | |
setError: (error: ErrorType) => void; | |
error: Error; | |
setError: (error: Error) => void; |
interface ErrorBoundaryProps { | ||
error: unknown; | ||
error: ErrorType; |
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.
error: ErrorType; | |
error: Error; |
@@ -15,11 +15,50 @@ import React, { | |||
// eslint-disable-next-line @typescript-eslint/ban-types | |||
type ComponentDidCatch = ComponentLifecycle<{}, {}>["componentDidCatch"]; | |||
|
|||
type ErrorType = WrappedError | Error | undefined; |
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.
type ErrorType = WrappedError | Error | undefined; |
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.
My reasoning behind this was to more clearly annotate the types for users, so that they know they can import WrappedError
and use instanceof
to check if .originalData
will be present.
I think we should still keep the | undefined
because when using TS in strict mode, undefined
and null
are no longer ignored by TS, and this could cause unexpected errors for users of the package with TS in strict mode (error === undefined
will be interpreted as never
as TS doesn't know this might be undefined
).
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.
Yes on the call sites where undefined is necessary -- I was thinking we could inline these.
My inclination is to keep WrappedError
private to the package for now. The property accessor is a nice convenience for local debugging.
Co-authored-by: Tate Thurston <[email protected]>
src/index.tsx
Outdated
let stringifiedData = | ||
"It was not possible to parse the data thrown as a string."; | ||
|
||
/* | ||
Some values cannot be converted into a string, such as Symbols | ||
or certain Object instances (e.g., `Object.create(null)`). | ||
|
||
This try/catch ensures that our silent error wrapper doesn't | ||
cause an unexpected error for the user, bricking the React app | ||
when we're meant to be preventing errors doing so. | ||
*/ | ||
try { | ||
stringifiedData = String(data); | ||
} catch { | ||
// Ignore errors | ||
} | ||
|
||
super(stringifiedData); | ||
|
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.
let stringifiedData = | |
"It was not possible to parse the data thrown as a string."; | |
/* | |
Some values cannot be converted into a string, such as Symbols | |
or certain Object instances (e.g., `Object.create(null)`). | |
This try/catch ensures that our silent error wrapper doesn't | |
cause an unexpected error for the user, bricking the React app | |
when we're meant to be preventing errors doing so. | |
*/ | |
try { | |
stringifiedData = String(data); | |
} catch { | |
// Ignore errors | |
} | |
super(stringifiedData); | |
/* | |
Some values cannot be converted into a string, such as Symbols | |
or certain Object instances (e.g., `Object.create(null)`). | |
This try/catch ensures that our silent error wrapper doesn't | |
cause an unexpected error for the user, bricking the React app | |
when we're meant to be preventing errors doing so. | |
*/ | |
try { | |
super(data); | |
} catch { | |
super("react-use-error-boundary: The thrown value could not be used to instantiate an instance of Error. The original thrown value can be accessed via the originalData property.") | |
} | |
this.name = 'WrappedError' |
The
error
value returned by the hook call is a boolean, not the actual caught error.The TS typings reflect this, but the readme documentation does not.