generated from bcgov/quickstart-openshift
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: CE-544 - create error boundary component (#344)
Co-authored-by: Mike Sears <[email protected]> Co-authored-by: Barrett Falk <[email protected]>
- Loading branch information
1 parent
cfdffe5
commit 3e9834c
Showing
6 changed files
with
272 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
frontend/src/app/components/error-handling/generic-error-boundary.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { FC, ReactNode } from "react"; | ||
import { useErrorBoundary } from "../../hooks/error-boundary"; | ||
import logo from "../../../assets/images/branding/CE-Temp-Logo.svg"; | ||
import { Footer } from "../containers/layout"; | ||
import { TbFaceIdError } from "react-icons/tb"; | ||
|
||
type props = { | ||
children?: ReactNode; | ||
}; | ||
|
||
const GenericErrorBoundary: FC<props> = ({ children }) => { | ||
const [error] = useErrorBoundary(); | ||
|
||
if (error) { | ||
return ( | ||
<div className="comp-container fixed-header"> | ||
{/* <!-- --> */} | ||
|
||
<div className="comp-header"> | ||
<div className="comp-header-logo comp-nav-item-icon-inverted"> | ||
<img | ||
className="logo-src" | ||
src={logo} | ||
alt="logo" | ||
/> | ||
</div> | ||
|
||
<div className="comp-header-content"> | ||
<div className="comp-header-left">{/* <!-- future left hand content --> */}</div> | ||
<div className="comp-header-right"> | ||
<div className="header-btn-lg pr-0"> | ||
<div className="widget-content p-0"> | ||
<div className="widget-content-wrapper"> | ||
<div className="widget-content-left">{/* <!-- search --> */}</div> | ||
<div className="widget-content-left"></div> | ||
<div className="widget-content-right"> | ||
{/* <!-- --> */} | ||
|
||
{/* <!-- --> */} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
{/* <!-- --> */} | ||
|
||
<div className="error-container"> | ||
<div className="message"> | ||
<TbFaceIdError /> | ||
{/* <br /> */} | ||
<h1 className="comp-padding-top-25">System error</h1>Please refresh the page to try again. If you still have | ||
problems, contact the Compliance & Enforcement Digital Services team at{" "} | ||
<a href="mailto:[email protected]">[email protected]</a> | ||
</div> | ||
</div> | ||
|
||
<Footer /> | ||
</div> | ||
); | ||
} | ||
|
||
return <>{children}</>; | ||
}; | ||
|
||
export default GenericErrorBoundary; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import React, { | ||
Component, | ||
useState, | ||
useCallback, | ||
createContext, | ||
useContext, | ||
MutableRefObject, | ||
useMemo, | ||
useRef, | ||
ComponentType, | ||
ReactNode, | ||
PropsWithChildren, | ||
ReactElement, | ||
ErrorInfo, | ||
} from "react"; | ||
|
||
type ComponentDidCatch = (error: Error, errorInfo: ErrorInfo) => void; | ||
|
||
interface ErrorBoundaryProps { | ||
error: Error | undefined; | ||
onError: ComponentDidCatch; | ||
} | ||
|
||
class ErrorBoundary extends Component<PropsWithChildren<ErrorBoundaryProps>> { | ||
displayName = "UseErrorBoundary"; | ||
|
||
componentDidCatch(...args: Parameters<NonNullable<Component["componentDidCatch"]>>) { | ||
// silence React warning: | ||
// ErrorBoundary: Error boundaries should implement getDerivedStateFromError(). | ||
// In that method, return a state update to display an error message or fallback UI | ||
this.setState({}); | ||
this.props.onError(...args); | ||
} | ||
|
||
render() { | ||
return this.props.children; | ||
} | ||
} | ||
|
||
const noop = () => false; | ||
|
||
interface ErrorBoundaryCtx { | ||
componentDidCatch: MutableRefObject<ComponentDidCatch | undefined>; | ||
error: Error | undefined; | ||
errorInfo: ErrorInfo | undefined; | ||
setError: (error: Error | undefined) => void; | ||
} | ||
|
||
const errorBoundaryContext = createContext<ErrorBoundaryCtx>({ | ||
componentDidCatch: { current: undefined }, | ||
error: undefined, | ||
errorInfo: undefined, | ||
setError: noop, | ||
}); | ||
|
||
// eslint-disable-next-line @typescript-eslint/ban-types | ||
export function ErrorBoundaryContext({ children }: { children?: ReactNode }) { | ||
const [error, setError] = useState<Error>(); | ||
const [errorInfo, setErrorInfo] = useState<ErrorInfo>(); | ||
|
||
const componentDidCatch = useRef<ComponentDidCatch>(); | ||
const ctx = useMemo( | ||
() => ({ | ||
componentDidCatch, | ||
error, | ||
errorInfo, | ||
setError, | ||
}), | ||
[error, errorInfo], | ||
); | ||
return ( | ||
<errorBoundaryContext.Provider value={ctx}> | ||
<ErrorBoundary | ||
error={error} | ||
onError={(error, errorInfo) => { | ||
setError(error); | ||
setErrorInfo(errorInfo); | ||
componentDidCatch.current?.(error, errorInfo); | ||
}} | ||
> | ||
{children} | ||
</ErrorBoundary> | ||
</errorBoundaryContext.Provider> | ||
); | ||
} | ||
ErrorBoundaryContext.displayName = "ErrorBoundaryContext"; | ||
|
||
export function withErrorBoundary<Props = Record<string, unknown>>( | ||
WrappedComponent: ComponentType<Props>, | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
): (props: PropsWithChildren<Props>) => ReactElement<any, any> { | ||
function WithErrorBoundary(props: Props) { | ||
return ( | ||
<ErrorBoundaryContext> | ||
<WrappedComponent | ||
key="WrappedComponent" | ||
{...props} | ||
/> | ||
</ErrorBoundaryContext> | ||
); | ||
} | ||
WithErrorBoundary.displayName = `WithErrorBoundary(${ | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
WrappedComponent.displayName ?? WrappedComponent.name ?? "Component" | ||
})`; | ||
|
||
return WithErrorBoundary; | ||
} | ||
|
||
export type UseErrorBoundaryReturn = [ | ||
error: Error | undefined, | ||
errorInfo: ErrorInfo | undefined, | ||
resetError: () => void, | ||
]; | ||
|
||
export function useErrorBoundary(componentDidCatch?: ComponentDidCatch): UseErrorBoundaryReturn { | ||
const ctx = useContext(errorBoundaryContext); | ||
ctx.componentDidCatch.current = componentDidCatch; | ||
const resetError = useCallback(() => { | ||
ctx.setError(undefined); | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
|
||
return [ctx.error, ctx.errorInfo, resetError]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,5 @@ | |
@import "./maps.scss"; | ||
@import "./carousel.scss"; | ||
@import "./form.scss"; | ||
@import "./errors.scss"; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
.error-container { | ||
margin-top: auto; | ||
position: relative; | ||
|
||
.message { | ||
position: absolute; | ||
top: 50%; | ||
left: 50%; | ||
width: 625px; | ||
transform: translateX(-50%); | ||
|
||
svg { | ||
height: 175px; | ||
width: 175px; | ||
float: left; | ||
padding-right: 25px; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters