본문으로 건너뛰기
KYH
  • Blog
  • About

joseph0926

I document what I learn while solving product problems with React and TypeScript.

HomeBlogAbout

© 2026 joseph0926. All rights reserved.

reactlearn_reactsuspense

Learn React 02: Why was Suspense introduced?

Learn why asynchronous functions are used for data fetching and why Suspense is introduced even though existing loading handling strategies exist.

Jul 13, 20255 min read
Learn React 02: Why was Suspense introduced?

1. Why asynchrony is essential

The fetch logic that receives data from the server on the web, etc. is basically an asynchronous function. This statement may seem obvious, but it's also good to ask the question, "Why must be an asynchronous function?"

When implementing a web/app, there are parts that the developer can control and parts that he cannot.

For example, developers can freely control whether to press a button to open an 'alert' window, but they cannot control whether data arrival is delayed or fails due to a problem with a network request.

동기 vs 비동기 처리 비교
버튼을 클릭하여 동기와 비동기 처리의 차이를 체험해보세요

동기적 처리 (나쁜 예)

3초간 UI가 완전히 멈춥니다

버튼이 멈추고 다른 상호작용 불가

비동기적 처리 (좋은 예)

3초간 로딩 표시만 보여집니다

UI는 반응하며 다른 상호작용 가능

테스트 카운터: 0

동기 처리와 비동기 처리시 아래 toast 버튼 클릭시 다른점을 확인해보세요

Let's assume that these uncontrollable parts operate synchronously.

Neither developers nor users want their web or app to freeze during data patching. Even if it stops, it should be so short that the user cannot feel it (usually 100 to 200 ms or less).

However, writing synchronously takes this control out of the developer's hands.

Each person has a different environment and device for accessing web/apps, and in some places, the network may be unstable and the speed may be very slow. In this case, the web/app will not work for a considerable period of time.

This is because in single-threaded JS, if network I/O is synchronous, the main thread cannot empty the call stack and the event loop does not run.

Therefore, to circumvent this problem, data patching should be done asynchronously. That is, data fetching logic should not block other logic.


2. Treat it like a synchronous flow with async/await

Because asynchronous logic alone makes the UI complex, JS has provided high-level syntax in the following order: callback → .then() → async/await.

EX

const data = await asynchronous function();
  • Return Promise just before await
  • The current execution context is removed from the call stack
  • When the results are ready, register a callback in Micro-task Queue
  • event loop runs again

Asynchronously avoids blocking problems and allows you to write synchronous logic using async/await.


3. Traditional method of loading state management

The next thing to consider is how to handle the UI while the data fetching logic is waiting for data, i.e. await.

전통적인 로딩 상태 관리
useState를 사용한 로딩 상태 관리 예시
const [loading, setLoading] = useState(false)
const [data, setData] = useState(null)

// 데이터 로드
setLoading(true)
fetchData()
  .then(setData)
  .finally(() => setLoading(false))

매번 로딩 상태를 수동으로 관리해야 함

에러 처리도 별도로 구현 필요

복잡한 UI에서는 여러 로딩 상태가 얽힘

React itself does not have a general data suspension API, so I used the following method:

  • Track loading state directly with useState
  • Utilize flags such as isPending provided by React Query
  • Data suspension dependent on framework (Next.js 13+, Relay, etc.)
// traditional way
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);

useEffect(() => {
  setLoading(true);
  fetchData()
    .then(setData)
    .finally(() => setLoading(false));
}, []);

if (loading) return <Spinner />;
return <div>{data}</div>;

Note: As of React 17, <Suspense> for code splitting existed, but Suspense for data fetching was only supported at the framework level.


4. Background of introduction of Suspense and React philosophy

However, the React team introduced the concept of Suspense (initially it had a narrow use, only used for lazy loads, etc.) and recommends it.

Suspense를 사용한 로딩 상태 관리
선언적인 방식으로 로딩 상태를 처리하는 예시
<Suspense fallback={<LoadingFallback />}>
  <UserProfile />
</Suspense>

// UserProfile 컴포넌트 내부
const userData = use(fetchUserData())

테스트2

test2@example.com

프론트엔드 개발자

로딩 상태를 선언적으로 처리

컴포넌트가 데이터에만 집중 가능

에러 바운더리와 함께 사용하여 에러 처리도 선언적으로

When I first encountered Suspense, I wondered, “Why do we need another concept when we can already control the loading state?” So, I looked for the RFC document that explains the background of Suspense introduction.

The core of Suspense is connected to the philosophy of React. React has the philosophy that “we just declare, and React performs the internal control.”

However, defining a loading flag directly and patterns such as “If loading is done in this part, this must be shown…” is somewhat at odds with the core concept of React.

According to the React philosophy, it makes more sense to declare "When loading occurs in this component, show this fallback UI." The concept that satisfies this is ‘Suspense’.

// Declarative approach using Suspense
<Suspense fallback={<Spinner />}>
  <DataComponent />
</Suspense>;

// Example of use() API usage in React 19
function DataComponent() {
  const data = use(fetchData()); // Reading Promises Directly
  return <div>{data}</div>;
}

Restrictions on using Suspense

According to React official documentation, only Suspense-enabled data sources are supported

  • Components loaded with React.lazy
  • Suspense support frameworks such as Next.js and Relay
  • React 19’s use() API
  • Directly throwing generic Promises is not officially supported.

5. Additional Benefits of Suspense

According to React Suspense official documentation, Suspense has the following advantages:

Maintain state only for committed components

Consistency is guaranteed because incomplete trees are not committed.

Integration with startTransition

You can prepare a new UI while maintaining the old UI.

Suspense + startTransition 조합
탭을 전환할 때 이전 컨텐츠를 유지하면서 새 컨텐츠를 로드합니다

프로젝트 개요

이 프로젝트는 React 19의 새로운 기능들을 활용한 모던 웹 애플리케이션입니다. Suspense와 startTransition을 통해 더 나은 사용자 경험을 제공합니다.

startTransition의 장점:

  • • 탭 전환 시 이전 컨텐츠가 즉시 사라지지 않음
  • • 로딩 중에도 UI가 반응성을 유지
  • • 사용자가 빠르게 탭을 전환해도 안정적
  • • 대기 중...
팁: 처음 방문하는 탭은 로딩이 표시되고, 이미 방문한 탭은 캐시된 데이터가 즉시 표시됩니다.

Natural integration with server components

Provides advantages for server-side rendering, such as streaming SSR.


6. Performance comparison: Traditional method vs Suspense

Let's visually compare how Suspense actually benefits you in terms of performance.

성능 비교: 전통적 방법 vs Suspense
동일한 데이터를 로드할 때 두 방식의 렌더링 차이를 비교합니다

전통적 방법(useState)

Suspense 방법

이 데모는 단순화된 예시입니다. 실제 애플리케이션에서는 React의 Concurrent 기능, 메모이제이션, 서버 컴포넌트 등이 성능에 더 큰 영향을 미칩니다.

Key performance differences

  1. Rendering Optimization

    • Traditional method: Re-render the entire component every time the loading state changes.
    • Suspense: React delays rendering to prevent unnecessary rendering of intermediate states.
  2. Reduce code complexity

    • Traditional method: Requires loading state management for each component
    • Suspense: Simplify code by reducing loading flag state distribution
  3. Code Splitting

    • Suspense integrates naturally with lazy to optimize bundle size

So in the end, why do we recommend using Suspense?

There are many reasons discussed above, but in fact, the key that I felt was most meaningful was to use it like React.
In the end, the core of React is “You (developer) just declare and I (React) will handle the rest.”
I think that the expansion of the concept of Suspense is innovative in that loading is not something I directly control, but all I have to do is declare "Show me this fallback UI if loading is in the part where loading exists."