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

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.
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.
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();
awaitAsynchronously avoids blocking problems and allows you to write synchronous logic using async/await.
The next thing to consider is how to handle the UI while the data fetching logic is waiting for data, i.e. await.
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:
useStateisPending provided by React Query// 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.
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 fallback={<LoadingFallback />}>
<UserProfile />
</Suspense>
// UserProfile 컴포넌트 내부
const userData = use(fetchUserData())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>;
}
According to React official documentation, only Suspense-enabled data sources are supported
use() APIAccording to React Suspense official documentation, Suspense has the following advantages:
Consistency is guaranteed because incomplete trees are not committed.
You can prepare a new UI while maintaining the old UI.
이 프로젝트는 React 19의 새로운 기능들을 활용한 모던 웹 애플리케이션입니다. Suspense와 startTransition을 통해 더 나은 사용자 경험을 제공합니다.
startTransition의 장점:
Provides advantages for server-side rendering, such as streaming SSR.
Let's visually compare how Suspense actually benefits you in terms of performance.
Rendering Optimization
Reduce code complexity
Code Splitting
lazy to optimize bundle sizeThere 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."