In React 18, is useEffect's function guaranteed to run AFTER a Suspense promise completes?

1.5k Views Asked by At

I have a simple master-detail scenario where on the left side, I load a list of cities using useSwr with a REST service, then on the right side I have a city detail windows that also uses useSwr to load a single city (either from clicked on left, or first load).

In the code below, I'm calling the useEffect function, and then using the data retrieved from useSwr in a state setting call (setSelectedCityId).

This works, and there has always been data associated with the cities array, but I'm wondering if it is guaranteed that useEffect's function will run AFTER the Suspense promise is completed (like it seems to be).

Here is my simple code:

import { Suspense, useEffect, useState, useTransition } from "react";
import useSwr from "swr";

const fetcher = (...args) => fetch(...args).then((res) => res.json());

function CityDetailFallback() {
  return <div>Loading (CityDetail)</div>;
}

function CityDetail({ selectedCityId }) {

  function CityDetailUI({ selectedCityId }) {
    const { data: city } = useSwr(
      selectedCityId
        ? `http://localhost:3000/api/city/${selectedCityId}`
        : null,
      fetcher,
      {
        suspense: true,
      }
    );
    
    if (!city) {
      return <div>loading city...</div>
    }
    
    return (
      <div className="row">
        <div className="col-9">
          <div>{JSON.stringify(city)} </div>
        </div>
      </div>
    );
  }

  return (
    <Suspense fallback={<CityDetailFallback />}>
      <CityDetailUI selectedCityId={selectedCityId}></CityDetailUI>
    </Suspense>
  );
}

function CityList({
  setSelectedCityId
}) {
  //
  const { data: cities } = useSwr("http://localhost:3000/api/city", fetcher, {
    suspense: true,
  });

  useEffect(() => {
    setSelectedCityId(cities[0].id);
  }, []);

  return (
    <div className="col-3">
      {cities.map((city) => {
        return (
          <div key={city.id}>
            <button
              onClick={(e) => {
                setSelectedCityId(city.id);
              }}
            >
              {city.city}
            </button>
          </div>
        );
      })}
    </div>
  );
}

export default function App() {
  const [selectedCityId, setSelectedCityId] = useState();

  return (
    <div className="container">
      <a href="/">Site Root</a>
      <hr />

      <Suspense fallback={<div>Loading...</div>}>
        <div className="row">
          <div className="col-3">
            <b>CITY LIST</b>
            <hr />
            <CityList
              setSelectedCityId={setSelectedCityId}
              selectedCityId={selectedCityId}
            />
          </div>
          <div className="col-9">
            <div>
              <b>CITY DETAIL (TOP ROW SELECTED AUTOMATICALLY)</b>
              <hr />
              <CityDetail selectedCityId={selectedCityId} />
            </div>
          </div>
        </div>
      </Suspense>
    </div>
  );
}

Note: I can't create a code sandbox because of a current bug in useSwr around suspense. https://github.com/vercel/swr/issues/1906 I'm testing currently with Create React App and using a dummy api endpoint for the REST calls. sorry :(

1

There are 1 best solutions below

0
On BEST ANSWER

Yes, in React 18 useEffect always runs when the tree is consistent. So effects fire only after the tree is ready and not suspended.