본문 바로가기
FrontEnd/React

Missing Suspense boundary with useSearchParams

by Fathory 2024. 7. 19.

발단

React로 웹 개발을 하다보면 url searchParams를 활용해서 데이터를 처리해야 하는 경우가 생기는데, IDE에서 이와 같은 에러를 내밀었습니다.
React는 비동기 작업을 처리하면서 사용자에게 일관된 경험을 제공하기 위해 Suspense 경계를 사용합니다

 

왜? 

 useSearchParams는 React Router의 훅 중 하나로, URL의 쿼리 파라미터를 읽고 설정하는 데 사용됩니다.

이 훅을 사용할 때, React는 비동기적으로 쿼리 파라미터를 처리하므로 Suspense boundary를 설정하지 않으면 "Missing Suspense boundary" 오류가 발생할 수 있습니다.
비동기 작업을 처리하는 동안, React는 사용자가 비동기 처리가 완료될 때까지 기다리도록 하는 기본적인 메커니즘이 필요합니다. 이 작업을 하지 않으면, 비정상적인 화면이 나타나거나, 화면이 깜빡거리는 등 사용자 경험에 좋지 않은 영향을 미치게 됩니다.

 

Suspense란?

Suspense는 React에서 비동기 작업을 처리하는 동안 로딩 상태를 관리하는 데 사용되는 컴포넌트입니다.
React 공식문서 Suspense
비동기 작업이 완료될 때까지 컴포넌트의 렌더링을 지연시키며, 로딩 스피너와 같은 대체 UI를 표시할 수 있습니다. Suspense를 사용하면 비동기 작업이 완료될 때까지 대체 UI를 노출하기 때문에, 미완성되거나 비정상적으로 보이는 UI를 숨길 수 있씁니다.

 

<Suspense> – React

The library for web and native user interfaces

react.dev


예시코드

예시코드를 통해서 알아보겠습니다.

import React from 'react';
import { useSearchParams } from 'react-router-dom';

const MyComponent = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  const updateSearchParams = () => {
    setSearchParams({ key: 'value' });
  };

  return (
    <div>
      <h1>Search Params Example</h1>
      <button onClick={updateSearchParams}>Update Search Params</button>
    </div>
  );
};

export default MyComponent;

위 코드에서 useSearchParams를 사용하고 있지만, Suspense boundary를 설정하지 않았기 때문에 Warning이 발생할 것입니다.


개선방법

이 문제를 해결하기 위해서 Suspense boundary를 설정하여 비동기 작업이 완료될 때까지 대기할 수 있도록 해보겠습니다.

import React, { Suspense } from 'react';
import { useSearchParams } from 'react-router-dom';

const MyComponent = () => {
  const [searchParams, setSearchParams] = useSearchParams();

  const updateSearchParams = () => {
    setSearchParams({ key: 'value' });
  };

  return (
    <div>
      <h1>Search Params Example</h1>
      <button onClick={updateSearchParams}>Update Search Params</button>
    </div>
  );
};

const App = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}> {/* <-- suspense 바운더리 추가 */}
      <MyComponent /> {/* <-- 기존 컴포넌트를 Suspense의 children으로 전달 */} 
    </Suspense>
  );
};

export default App;

위 코드에서 Suspense 컴포넌트를 사용하여 MyComponent를 감싸주었습니다.

이렇게 하면 React는 useSearchParams가 비동기 작업을 완료할 때까지 대기할 수 있게 됩니다.

Suspense 컴포넌트의 fallback 속성에는 비동기 작업이 완료될 때까지 보여줄 로딩 UI를 지정했습니다. fallback 속성의 타입은 ReactNode로 개발자가 원하는 UI를 만들어서 보여줄 수 있습니다. 일반적으로는 Skeleton UI또는 Loading Spinner를 많이 사용합니다.

 

Suspense의 다른 사용 사례

Suspense는 useSearchParams 외에도 다양한 비동기 작업에 사용할 수 있습니다.

예를 들어, 데이터 페칭(api 요청, 이미지 로딩 등 비동기처리가 되는 다양한 상황에서 사용할 수 있습니다.

데이터 페칭 (Data Fetching)

import React, { Suspense } from 'react';

const UserProfile = React.lazy(() => import('./UserProfile'));

const App = () => {
  return (
    <Suspense fallback={<div>Loading user profile...</div>}>
      <UserProfile />
    </Suspense>
  );
};

export default App;


서버에서 데이터를 가져오는 동안 로딩 상태를 표시할 때 Suspense를 사용할 수 있습니다
사용자 프로필 데이터를 불러오는 경우, 데이터가 로딩 중일 때는 스피너를 보여주고, 데이터가 로드되면 실제 프로필 정보를 보여줍니다.

코드 스플리팅 (Code Splitting)

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

const App = () => {
  return (
    <Suspense fallback={<div>Loading component...</div>}>
      <OtherComponent />
    </Suspense>
  );
};

export default App;​

특정 컴포넌트를 필요할 때만 로드하도록 하는 코드 스플리팅에서도 Suspense를 사용할 수 있습니다.
불필요한 컴포넌트를 필요할 때만 로드하기 때문에, 초기 로드 시간을 줄일 수 있습니다.

이미지 로딩 (Image Loading)
import React, { Suspense } from 'react';

const ImageComponent = React.lazy(() => import('./ImageComponent'));

const App = () => {
  return (
    <Suspense fallback={<div>Loading image...</div>}>
      <ImageComponent />
    </Suspense>
  );
};

export default App;


대용량 이미지를 로딩할 때도 Suspense를 사용할 수 있어요. 이미지를 비동기적으로 로드하고, 로딩 중에는 플레이스홀더를 표시할 수 있답니다.

서드파티 라이브러리 로딩 (Third-party Library Loading)

import React, { Suspense } from 'react';

const ChartComponent = React.lazy(() => import('./ChartComponent'));

const App = () => {
  return (
    <Suspense fallback={<div>Loading chart...</div>}>
      <ChartComponent />
    </Suspense>
  );
};

export default App;

서드파티 라이브러리를 비동기적으로 로드하고, 로드가 완료되면 해당 라이브러리를 사용도록 렌더링 할 수도 있습니다.
 

라우트 기반 코드 스플리팅 (Route-based Code Splitting)

import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = React.lazy(() => import('./Home'));
const About = React.lazy(() => import('./About'));

const App = () => {
  return (
    <Router>
      <Suspense fallback={<div>Loading page...</div>}>
        <Switch>
          <Route path="/home" component={Home} />
          <Route path="/about" component={About} />
        </Switch>
      </Suspense>
    </Router>
  );
};

export default App;


React Router와 함께 사용해서 특정 라우트에 접근할 때만 해당 컴포넌트를 로드하는 방식으로 코드 스플리팅을 할 수도 있습니다.
이 경우 라우트가 로드될 때까지 Suspense로 로딩 상태를 표시할 수 있습니다

* 여기에 작성된 React.lazy()는 NextJS의 dynamic 메소드와 동일한 역할을 합니다. 

결론

React에서 useSearchParams를 사용할 때는 Suspense Boundary를 설정하는 것이 좋습니다.

 비동기작업에서 나타나는 화면의 깜빡임이나 레이아웃의 Reflow 등 여러가지 불안정한 요소들을 제거하기 위해서 Suspense Boundary를 사용하는 것이 좋겠습니다. 
 시작은  Missing Suspense boundary with useSearchParams 이게 뭔지 알아보는 것이었지만, 결론적으로는 Suspense Boundary에 대한 글이 되었습니다.

자세한 내용은 모르고, IDE에서 던져주는 워닝 메시지만 보고 시작된 블로깅인데, 사용자에게 도달하는 앱의 품질을 높이려면 비동기 처리가 필요한 로직에서는 Suspense를 반드시 사용하는게 좋겠습니다.

 

* NextJS - app router를 사용할 때에는 loading.tsx파일을 생성하면 자동으로 suspense 처리를 해주더랍니다. 


참고자료 
https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout

 

Missing Suspense boundary with useSearchParams

Using App Router Features available in /app

nextjs.org

 

반응형