reactjsnext.jsvercel

How to add Suspense boundary to useSearchParams use case in useEffect hook


Hi sorry for the terrible use of the word use in the title. I am getting the following error when deploying my project via Vercel (running fine locally).

Error:

useSearchParams() should be wrapped in a suspense boundary at page "/rooms/cinema/movie". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout

But I don't think adding the Suspense tag worked as I call the useSearchParams hook inside the useEffect hook.... where do I need to add the Suspense tag?

Code:

// ./movie/page.jsx

'use client'

import React, { useEffect, useState, Suspense } from 'react';
import { useSearchParams } from 'next/navigation';
import moviesData from "/public/JSON/movies.json";
import Link from 'next/link';
import Stars from './Stars';


export default function Movie() {
  const searchParams = useSearchParams();
  const [movieName, setMovieName] = useState("Unknown");
  const [movieData, setMovieData] = useState({});
  const [movieTags, setMovieTags] = useState([]);

  useEffect(() => {
    const title = searchParams.get('title');
    if (title) {
      setMovieName(title);
    }
  }, [searchParams]);

  useEffect(() => {
    if (movieName && moviesData[movieName]) {
      setMovieData(moviesData[movieName]);
      setMovieTags(moviesData[movieName].tags);
    } else {
      setMovieData({}); // Reset if movieName is invalid
      setMovieTags([]);
    }
  }, [movieName]);

  return (
    <Suspense>
    <body className='cinema'>
        <Link className="link" href="/"> &lt; Home </Link>
        <Link className="link" href="/rooms/cinema/"> &lt; Cinema </Link>
        <div className='moviePage'>
            <div className='moviePageImg'>
                <img className="moviePoster" src={movieData.image}></img>
            </div>
            <div className='moviePageData'>
                <h1>{movieName}</h1>
                <h2>{movieData.director}</h2>
                <div> <Stars rating={movieData.stars}/> </div> <br/>
                <div id='synopsis'> {movieData.synopsis} </div> <br/>
                <div id='review'> {movieData.review} </div> <br/>
                <ul id='tags'>
                    {/* {movieData.tags} */}
                    {movieTags.map((tag)=> <li className='tag' key={tag}>{tag}</li>)}
                </ul>
            </div>
        </div>
      <div className="fuzzy-overlay"></div>
    </body>
    </Suspense>
  );
}

Repository if interested: https://github.com/SengulC/unboxd


Solution

  • For your example, the Suspense boundary must exist above the component that is throwing a promise; i.e. if you're looking to maintain the structure of your page, create a layout under the movie directory to neighbor closest to your page:

    // ./movie/layout.tsx
    
    type LayoutProps = {
      children: React.ReactNode
    }
    
    export default async function Layout({ children }: LayoutProps) {
      return (
        <Suspense fallback={...}>
          { children }
        </Suspense>
      )
    }