DEV Community

Cover image for Error handling for production application in Preact
Amin
Amin

Posted on

Error handling for production application in Preact

Preact is a great alternative to React. It is made for creating modern Web Application and weights a ton less than a React application.

It is possible de raise and catch errors in JavaScript. This is useful when a function raises an error and we want more control over its execution.

You can also catch errors in Preact, using a built-in hook. Let's see how to use it.

useErrorBoundary

The useErrorBoundary hook is used to handle every exception that may be raised by child components inside of a component.

This can be useful if you use it in your main component, in combination with a remote service like Sentry to send any error reporting instead of letting it go silent in the client application.

import { render, Fragment } from "preact";
import { useErrorBoundary } from "preact/hooks";

const MaybeText = ({ children }) => {
  if (Math.random() > 0.5) {
    return children;
  }

  throw new Error("Unable to display the text.");
};

const Main = () => {
  const [error] = useErrorBoundary();

  if (error) {
    return (
      <Fragment>
        <h1>Error</h1>
        <p>An error occurred.</p>
        <small>Reason: {error.message}</small>
      </Fragment>
    );
  }

  return (
    <Fragment>
      <h1>Main</h1>
      <p>This is the main component.</p>
      <MaybeText>Maybe this will be printed?</MaybeText>
    </Fragment>
  );
};

const root = document.getElementById("root");

if (root) {
  render(<Main />, root);
}
Enter fullscreen mode Exit fullscreen mode

Here, we defined a child component that displays some text on a random basis. When it can't, it will throw an error. useErrorBoundary will help us catch this error and handle it as we want.

The error is exactly the object you would get if you were to use a catch block instead. It is an instance of the Error class. So you can use all of the properties and methods that you can use on this class instance.

resetError

The reason why we destructured it is because you can also use a second parameter to try to render the child component that caused an issue.

import { render, Fragment } from "preact";
import { useErrorBoundary } from "preact/hooks";

const MaybeText = ({ children }) => {
  if (Math.random() > 0.5) {
    return children;
  }

  throw new Error("Unable to display the text.");
};

const Main = () => {
  const [error, resetError] = useErrorBoundary();

  if (error) {
    return (
      <Fragment>
        <h1>Error</h1>
        <p>An error occurred.</p>
        <small>Reason: {error.message}</small>
        <button onClick={resetError}>Try again</button>
      </Fragment>
    );
  }

  return (
    <Fragment>
      <h1>Main</h1>
      <p>This is the main component.</p>
      <MaybeText>Maybe this will be printed?</MaybeText>
    </Fragment>
  );
};

const root = document.getElementById("root");

if (root) {
  render(<Main />, root);
}
Enter fullscreen mode Exit fullscreen mode

We simply added another variable in our destructuration to be able to reset our error and we used a button to provide the user the ability to try and render the component that failed again.

This can be great if your component can throw an error, but is recoverable like a network failure, or a long process that ended suddenly.

Sentry

You should also have registered to some third-party service like Sentry to help you catch errors even in production. This can be very useful to catch some errors that didn't appear in development but rather in production on a real client browser.

import { render, Fragment } from "preact";
import { useErrorBoundary, useEffect } from "preact/hooks";

const MaybeText = ({ children }) => {
  if (Math.random() > 0.5) {
    return children;
  }

  throw new Error("Unable to display the text.");
};

const Main = () => {
  const [error, resetError] = useErrorBoundary();

  useEffect(() => {
    if (error) {
      fetch("https://sentry.io", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          message: error.message
        })
      });
    }
  }, [error]);

  if (error) {
    return (
      <Fragment>
        <h1>Error</h1>
        <p>An error occurred.</p>
        <small>Reason: {error.message}</small>
        <button onClick={resetError}>Try again</button>
      </Fragment>
    );
  }

  return (
    <Fragment>
      <h1>Main</h1>
      <p>This is the main component.</p>
      <MaybeText>Maybe this will be printed?</MaybeText>
    </Fragment>
  );
};

const root = document.getElementById("root");

if (root) {
  render(<Main />, root);
}
Enter fullscreen mode Exit fullscreen mode

This is not how you are supposed to consume the Sentry API, I simply made that up for the sake of this example, you should probably check the documentation before using it first.

This is very easy to do. We simply added a useEffect that will be triggered whenever the error is updated. We check whether there is an error (or not) before sending it to our remove service. We don't care about the resolution of the promise because if there is an error with the fetch call, there is nothing much we can do about it in production (this can be caused by the service or the network being down).

useErrorBoundary parameter

Since this pattern of checking that there is an error, and sending a request to the service is to be expected, there is actually a parameter in the useErrorBoundary that let us define a function that will be called each time there is an error. This means that our code can be simplified to this.

import { render, Fragment } from "preact";
import { useErrorBoundary } from "preact/hooks";

const MaybeText = ({ children }) => {
  if (Math.random() > 0.5) {
    return children;
  }

  throw new Error("Unable to display the text.");
};

const Main = () => {
  const [error, resetError] = useErrorBoundary(error => {
    fetch("https://sentry.io", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        message: error.message
      })
    });
  });

  if (error) {
    return (
      <Fragment>
        <h1>Error</h1>
        <p>An error occurred.</p>
        <small>Reason: {error.message}</small>
        <button onClick={resetError}>Try again</button>
      </Fragment>
    );
  }

  return (
    <Fragment>
      <h1>Main</h1>
      <p>This is the main component.</p>
      <MaybeText>Maybe this will be printed?</MaybeText>
    </Fragment>
  );
};

const root = document.getElementById("root");

if (root) {
  render(<Main />, root);
}
Enter fullscreen mode Exit fullscreen mode

No need for the useEffect call now, this is strictly equivalent to what we did earlier.

That's it, nothing very difficult. The most difficult thing to do here is registrating to some service like Sentry. You have everything you need in this post to start monitoring errors in your application in production.

Discussion (0)