javascripthtmlreactjs

How i can get suspense to work on react 18?


Hello my code is as follow wnna try suspense with react 18 but not having luck to render Loading....

User.jsx

import React, { useEffect, useState } from "react";

export const User = () => {
  const [user, setuser] = useState(null);

  useEffect(() => {
    const res = fetch("https://jsonplaceholder.typicode.com/todos/1")
      .then((response) => response.json())
      .then((json) => setuser(json));
  }, []);

  return <div>{JSON.stringify(user)}</div>;
};

App.js

import logo from './logo.svg';
import './App.css';
import { Suspense } from 'react';
import { User } from './components/User';

function App() {
  return (
    <div className="App">
   <Suspense fallback={<p>Loading.....</p>}>
     <User/>
   </Suspense>
    </div>
  );
}

export default App;

the app renders {} or null but never getting the fallback as expected


Solution

  • Suspense is a very smart paradigm based on fetch-while-render strategy. This means that React Components from now on are able to consume directly asynchronous data. And will render the fallback while the data promise is not resolved.

    The only issue is that you can't pass directly a promise, but you need a wrapper that transforms it into a Suspense consumable entity. Basically the React Component wrapped in Suspense tags, will start to try to render continuosly and it expects for a method that throws a new promise until the original promise is not resolved, that's how it knows that it has to keep rendering the fallback. So you need to pass a resource with a very specific shape, that's why you need a wrapper. Fetching libraries like react-queris or RTK-query will implement the wrapper themselves, so you won't have to care of that part, it's not something you will have to do manually, but still if you'd like to see what's under the hood, this would be a very basic implementation of a Suspense ready fetching library:

    import React, { useEffect, useState, Suspense, useRef } from 'react';
    
    export default function App() {
      return (
        <div className="App">
          <Suspense fallback={<p>Loading.....</p>}>
            <User />
          </Suspense>
        </div>
      );
    }
    
    export const User = () => {
      const data = useGetData('https://jsonplaceholder.typicode.com/todos/1');
      return <div>{JSON.stringify(data)}</div>;
    };

    This is the custom hook useGetData I made:

    // VERY BASIC IMPLEMENTATION OF A FETCHING LIBRARY BASED ON SUSPENSE
    
    // This is the official basic promiseWrapper they implement in React Suspense Demo:
    
    function wrapPromise(promise) {
      let status = 'pending';
      let result;
      let suspender = promise.then(
        (r) => {
          status = 'success';
          result = r;
        },
        (e) => {
          status = 'error';
          result = e;
        }
      );
      return {
        read() {
          //console.log(status);
          if (status === 'pending') {
            throw suspender;
          } else if (status === 'error') {
            throw result;
          } else if (status === 'success') {
            return result;
          }
        },
      };
    }
    
    /* Basic fetching function */
    
    const fetcher = async (url) => {
      try {
        const res = await fetch(url);
        const data = await res.json();
        await delay(2000);
        return data;
      } catch (e) {
        throw e;
      }
    };
    
    /* Util to delay loading */
    
    const delay = (d) => new Promise((r) => setTimeout(r, d));
    
    
    /* HOOK that lets to start the fetch promise only on component mount */
    /* It's based on "wrapPromise" utility, which is MANDATORY to return a Suspense consumable entity */
    
    const useGetData = (url) => {
      const [resource, setResource] = useState(null);
      useEffect(() => {
        const _resource = wrapPromise(fetcher(url));
        setResource(_resource);
      }, []);
    
      return resource?.read();
    };

    Suspense The working implementation is HERE

    If you want to experiment how a Suspense-ready library works, you can check this example that makes use of the great state-manager valtio which is extremely light weight and easy to use, and suspense-ready. It let's you just pass a promise in your store, and when you try to access it in a component, the component will fallback if wrapped in <Suspense>. Check it HERE