Error Handling in Cal.com
[Alert component] is used to show errors:
In this guide, we analyze how errors are handled in a component in cal.com.
How to find out what error handling mechanisms are used in cal.com codebase? Pick a page on cal.com and find the page route in its source code
In this guide, we choose the bookings listing component.
[Insert screenshots of bookings page]
Bookings component depends purely on the query
result to decide what to render based on query.status
.
// picked from https://github.com/calcom/cal.com/blob/main/apps/web/modules/bookings/views/bookings-listing-view.tsx#L82C3-L95C5
const query = trpc.viewer.bookings.get.useInfiniteQuery(
{
limit: 10,
filters: {
...filterQuery,
status: filterQuery.status ?? status,
},
},
{
// first render has status `undefined`
enabled: true,
getNextPageParam: (lastPage) => lastPage.nextCursor,
}
);
In this guide, our focus is only on how errors are handled in a component. So, if there's an error encountered, query.status would be "error".
{
query.status === "error" &&
(
<Alert severity="error" title={t("something_went_wrong")} message={query.error.message} />
)
}
Alert is imported from
packages/ui/components/alert/Alert.tsx
References:
- https://github.com/calcom/cal.com/blob/main/apps/web/pages/bookings/%5Bstatus%5D.tsx
- https://github.com/calcom/cal.com/blob/main/apps/web/modules/bookings/views/bookings-listing-view.tsx#L165
- https://github.com/calcom/cal.com/blob/main/packages/ui/components/alert/Alert.tsx // In auth/login error handling:
- https://github.com/calcom/cal.com/blob/main/apps/web/modules/auth/login-view.tsx#L232C1-L233C80
[Error tracking]: Cal.com uses Sentry to track production errors. The below code snippet is from cal.com/apps/web/intrumentation.ts
import * as Sentry from "@sentry/nextjs";
export function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
});
}
if (process.env.NEXT_RUNTIME === "edge") {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
});
}
}
Read this official Sentry docs that explains how to setup Sentry in a Next.js project.
This intrumentation file above is automatically generated as part of Sentry setup.
dsn
:
The DSN tells the SDK where to send the events. If this value is not provided, the SDK will try to read it from the SENTRY_DSN environment variable. If that variable also does not exist, the SDK will just not send any events.
In runtimes without a process environment (such as the browser) that fallback does not apply.
Learn more about DSN utilization.
References:
- https://github.com/calcom/cal.com/blob/main/apps/web/instrumentation.ts
- https://github.com/calcom/cal.com/blob/main/apps/web/sentry.client.config.ts
- https://docs.sentry.io/platforms/javascript/guides/nextjs/
[Error Boundary]
By default, if your application throws an error during rendering, React will remove its UI from the screen. To prevent this, you can wrap a part of your UI into an error boundary. An error boundary is a special component that lets you display some fallback UI instead of the part that crashed—for example, an error message.
React docs explains how to write your own boundary
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logErrorToMyService(error, info.componentStack);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return this.props.fallback;
}
return this.props.children;
}
}
You can write your own ErrorBoundary or use react-error-boundary, recommended by React.
Guess the kind of ErrorBoundary used in cal.com?
import type { ErrorInfo } from "react";
import React from "react";
class ErrorBoundary extends React.Component<
{ children: React.ReactNode; message?: string },
{ error: Error | null; errorInfo: ErrorInfo | null }
> {
constructor(props: { children: React.ReactNode } | Readonly<{ children: React.ReactNode }>) {
super(props);
this.state = { error: null, errorInfo: null };
}
componentDidCatch?(error: Error, errorInfo: ErrorInfo) {
// Catch errors in any components below and re-render with error message
this.setState({ error, errorInfo });
// You can also log error messages to an error reporting service here
}
render() {
// do not intercept next-not-found error, allow displaying not-found.tsx page when notFound() is thrown on server side
if (
this.state.error !== null &&
"digest" in this.state.error &&
this.state.error.digest === "NEXT_NOT_FOUND"
) {
return this.props.children;
}
if (this.state.errorInfo) {
// Error path
return (
<div>
<h2>{this.props.message || "Something went wrong."}</h2>
<details style={{ whiteSpace: "pre-wrap" }}>
{this.state.error && this.state.error.toString()}
</details>
</div>
);
}
// Normally, just render children
return this.props.children;
}
}
export default ErrorBoundary;
cal.com has its own ErrorBoundary class. It has componentDidCatch
and render
methods defined. Although, documentation
provides the example containing getDerivedStateFromError
, it looks like this getDerivedStateFromError
method is not
used in cal.com ErrorBoundary class.
References:
- https://github.com/search?q=repo%3Acalcom%2Fcal.com%20ErrorBoundary&type=code
- https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
- https://github.com/bvaughn/react-error-boundary
- https://github.com/calcom/cal.com/blob/5bbccad41d625250ac03a6683e751b8ed6650721/packages/ui/components/errorBoundary/ErrorBoundary.tsx#L4