Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
23 Sep 2025 · 6 min read ·Article 85 / 125
Go

85 Handling Errors and Loading State on the Frontend

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

85 Handling Errors and Loading State on the Frontend

Error handling and loading state are two things that are inseparable from modern frontend development. In order to improve UX (user experience) quality, both errors and loading need to be handled consistently and elegantly in every web or mobile application. I believe that the quality of error and loading handling is far more fundamental than the animated effects that are sometimes just “decoration.”

In this article, I’ll share 85 best practices, insights, and code snippets on how to handle errors and loading state on the frontend, complete with React code examples, flow diagrams, and simulations. The goal: to make our applications more robust, secure, and pleasant to use.


Why Are Error and Loading State Important?

Let’s start with two important questions:

  1. What happens if loading isn’t indicated to the user?
  2. What if an error occurs but isn’t handled?

The answer is simple: the user will feel like the application is “frozen,” or worse—they’ll just leave, abandoning the application and the developer’s reputation.

Error and loading state aren’t just a visual concern, but also part of our application’s reliability (GX — “Genuine Experience”).


Ways to Handle Error and Loading State

Here is the most fundamental mantra you must hold onto:

Danger
“Always assume the API can fail. Always inform the user of status changes.”

We can break down the techniques:

StateDescriptionVisual Example
LoadingWaiting for data/fetch processSpinner, Skeleton, Dots
SuccessData successfully received & displayedData view
ErrorA failure from the backend/network/validationBanner/Toast/Error Page/Retry
EmptyNo data exists, but it’s not an errorEmpty illustration, Call to Action

Basic Implementation of Error & Loading Handling in React

Let’s jump straight into a React code example (the principle is nearly the same across all SPA frameworks):

jsx
 1import { useEffect, useState } from "react";
 2
 3function fetchData() {
 4  return new Promise((resolve, reject) => {
 5    setTimeout(() => {
 6      // Simulation: random success/error.
 7      Math.random() > 0.3
 8        ? resolve(["Apple", "Banana", "Orange"])
 9        : reject(new Error("Failed fetch data!"));
10    }, 1000);
11  });
12}
13
14export function FruitList() {
15  const [loading, setLoading] = useState(true);
16  const [data, setData] = useState([]);
17  const [error, setError] = useState(null);
18
19  useEffect(() => {
20    fetchData()
21      .then((fruits) => {
22        setData(fruits);
23        setLoading(false);
24      })
25      .catch((err) => {
26        setError(err.message);
27        setLoading(false);
28      });
29  }, []);
30
31  if (loading) return <div>Loading...</div>;
32  if (error)   return <div style={{color:"red"}}>Oops: {error}</div>;
33  if (data.length === 0) return <div>No fruits found.</div>;
34
35  return (
36    <ul>
37      {data.map(fruit => <li key={fruit}>{fruit}</li>)}
38    </ul>
39  );
40}

Notes:

  • We use three states: loading, data, and error.
  • Once the fetch is done, loading is turned off. If there’s an error, display the error message.
  • On success but with empty data, show an empty-data message.

Best Practice: Have a Single Source of Truth for “Status”

I recommend encapsulating the state into a single status object:

javascript
1const [state, setState] = useState({
2  status: "idle", // or: 'loading', 'success', 'error', 'empty'
3  data: null,
4  error: null
5});

The flow looks roughly like this:

MERMAID
flowchart TD
    A[Memulai Fetch] --> B{Sukses?}
    B -- Ya --> C[Cek Data Kosong?]
    C -- Tidak--> D[Status = Success]
    C -- Ya --> E[Status = Empty]
    B -- Tidak --> F[Status = Error]

A List of “85 Styles” of Handling Errors & Loading on the Frontend

To make this article more practical, here I’ve summarized ideas and best practices related to error and loading state in the form of a table of scenarios and their techniques—short and to the point, 85 of them:

#Scenario/PrincipleHandling Approach
1Fetch APIShow a loading spinner
2Fetch API errorShow a retry button and error message
3Submit form (POST/PUT)Disabled button + loading indicator
4Submit error (e.g. validation)Highlight the field error
5Empty state (no data)Show an illustration + CTA (action)
6Skeleton loadingUse a skeleton when fetching a list/table
7Network errorShow an error toast/banner
8TimeoutRetry or fallback (cached)
9Slow connectionShow a “slow network” warning/loading
10Optimistic UI (change state before success)Revert on failure and show the error
11Error boundary (React etc)Show a friendly fallback UI
12Multiple parallel requestsPer-state loading & error
13Error 401/403Force logout / redirect to login
14Error 404Show a custom Not Found page
15Error trackingSend the error to a monitoring tool
16Polling dataEnsure loading & error state on each tick
84Layered loading (global & local)Global loader in the navbar/app shell, local loader in components
85Accessible loading animationUse role=“status”/aria-live so screen readers announce changes

(The full table can be downloaded on Github ; it contains popular error/loading scenarios on the frontend.)


Simulation: Layered Error Handling in a Dashboard

For example, suppose a dashboard has several widgets that fetch data asynchronously. Let’s simulate error and loading handling per widget:

jsx
 1function Widget({title, fetchFn}) {
 2  const [state, setState] = useState({ status: 'loading', data: null, error: null});
 3  useEffect(() => {
 4    fetchFn()
 5      .then((data) => setState({status: data.length ? 'success' : 'empty', data, error: null}))
 6      .catch((err) => setState({status: 'error', data: null, error: err.message}));
 7  }, [fetchFn]);
 8  
 9  if (state.status === 'loading') return <div>{title}: Loading...</div>;
10  if (state.status === 'error')   return <div>{title}: <span style={{color: 'red'}}>Error: {state.error}</span></div>;
11  if (state.status === 'empty')   return <div>{title}: No data</div>;
12  return <div>{title}: Data Ready </div>;
13}
14
15function Dashboard() {
16  const fetchA = () => fetchData();
17  const fetchB = () => Promise.reject(new Error("Service Down"));
18  return (
19    <div>
20      <h2>Dashboard</h2>
21      <Widget title="Widget A" fetchFn={fetchA} />
22      <Widget title="Widget B" fetchFn={fetchB} />
23    </div>
24  );
25}

Error Boundary in React (For Robust Render Errors)

For errors that occur while rendering a component, use an error boundary:

jsx
 1class ErrorBoundary extends React.Component {
 2  state = { hasError: false, error: null };
 3  
 4  static getDerivedStateFromError(error) {
 5    return {hasError: true, error};
 6  }
 7
 8  componentDidCatch(error, errorInfo) {
 9    // Send to Sentry/monitoring, etc.
10    logErrorToService(error, errorInfo);
11  }
12
13  render() {
14    if (this.state.hasError) return <div>Oh Snap! Something went wrong.</div>;
15    return this.props.children;
16  }
17}
Use the wrapper in the App component:

jsx
1<ErrorBoundary>
2  <App />
3</ErrorBoundary>

Accessible Loading State

Make sure the loader is readable by screen readers:

jsx
1<div role="status" aria-live="polite">
2  Loading fruits, please wait...
3</div>

Conclusion: Handling Errors & Loading Is 80% UX!

Besides paying attention to the main flow, error and loading state are the “backend theater” that we need to polish. With the 85 principles and techniques above, your application is guaranteed to look more professional, more reliable—and more loved by users!

Final Tips:

  • Don’t show technical errors directly to the user (e.g., a stacktrace).
  • Provide a “retry,” and educate the user when needed.
  • Build a reusable component for status handling!

Share your error/loading handling experiences in the comments!


See you in the next article. Happy Error Handling! 🚀


References:

For other articles about frontend best practices, see my personal profile on Medium.

Related Articles

💬 Comments