DEV Community

Riku Rouvila
Riku Rouvila

Posted on

Show different error message in React based on fetch status code

So you want to show a different error message in React based on the HTTP status code you got? Try this:

import * as React from "react";

class InvalidRequestError extends Error {
  readonly type = "InvalidRequestError" as const;
}
class UnexpectedError extends Error {
  readonly type = "UnexpectedError" as const;
}

type ResponseError = UnexpectedError | InvalidRequestError;

export default function App() {
  const [error, setError] = React.useState<ResponseError>();

  React.useEffect(() => {
    async function doRequest() {
      const response = await fetch("https://httpstat.us/400");
      if (response.ok) {
        // Store response body normally
        return;
      }
      if (response.status === 400) {
        setError(new InvalidRequestError());
      } else {
        setError(new UnexpectedError());
      }
    }
    doRequest();
  }, []);

  return (
    <div className="App">
      <h1>Error</h1>
      <h2>Status type was: {error && error.type}</h2>
      {error && error.type === "InvalidRequestError"
        ? "Invalid request"
        : "Unexpected error"}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why not just store the status code in the component's state?

You totally can. This is just an abstraction one level higher from that. The goal later on for me is to move these fetch-calls to a new module – a module that would only talk in terms of domain language, so:

export async function getUser(id: number) : Promise<User> {
  const response = await fetch("https://api.example.com/user/" + id);
  if (response.ok) {
    // Return the found user object
    return;
  }
  if (response.status === 404) {
    throw new UserNotFound();
  } else {
    throw new UnexpectedError();
  }
}
Enter fullscreen mode Exit fullscreen mode

See what happens here? The caller of this function doesn't (and shouldn't) know anything about HTTP or where the user was gotten from. It could've come through WebSockets for all we know. This means the caller wouldn't have access to the HTTP statuses anymore either, which is a good thing so our HTTP layer doesn't leak to our view layer.

So our first example would turn into something like this

import * as React from "react";
import { getUser, ResponseError } from "./services/users";

export default function App() {
  const [error, setError] = React.useState<ResponseError>();

  React.useEffect(() => {
    async function doRequest() {
      try {
        const user = await getUser(123);  
        // Store the user normally
      } catch (error) {
        setError(error)
      }
    }
    doRequest();
  }, []);

  return (
    <div className="App">
      <h1>Error</h1>
      <h2>Status type was: {error && error.type}</h2>
      {error && error.type === "InvalidRequestError"
        ? "Invalid request"
        : "Unexpected error"}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)