Skip to content

Instantly share code, notes, and snippets.

@khanzzirfan
Created September 26, 2021 02:33

Revisions

  1. khanzzirfan created this gist Sep 26, 2021.
    104 changes: 104 additions & 0 deletions contraReactFetchSuspense.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    /**
    * Since solution using Suspense API we should be looking at Render-as-you-fetch pattern.
    * Start fetching all the required data for the next screen as early as possible, and start rendering the new screen immediately.
    *
    * The 3-state pattern is very common for loading any resources.
    * It is in fact so common that it has a name.
    * Any object containing a read function that behaves like this 3-state pattern, is called a resource.
    *
    * Issues
    * 1) Suspense tag missing attribute 'fallback' prop
    * 2) Data is not being fetched early
    * 3) Fetching data on useEffect might cause Race condition. using UseEffect is like Fetch-Then-Render pattern we should avoid.
    *
    * Optional but Useful: Handling Errors
    * Since we are using Promises, we might use catch() to handle errors.
    * Use ErrorBoundary class and then we can put it anywhere in the tree to catch errors
    */

    import React, { Suspense } from "react";

    // Error boundaries currently have to be classes.
    class ErrorBoundary extends React.Component {
    state = { hasError: false, error: null };
    static getDerivedStateFromError(error) {
    return {
    hasError: true,
    error
    };
    }
    render() {
    if (this.state.hasError) {
    return this.props.fallback;
    }
    return this.props.children;
    }
    }

    function fetchProfileData(userId) {
    let userPromise = fetchUserProfilePromise(userId);
    return wrapPromise(userPromise);
    }

    // Suspense integrations like Relay implement
    // a contract like this to integrate with React.
    // Real implementations can be significantly more complex.
    // Don't copy-paste this into your project!
    function wrapPromise(promise) {
    let status = "pending";
    let result;
    let suspender = promise.then(
    (r) => {
    status = "success";
    result = r;
    },
    (e) => {
    status = "error";
    result = e;
    }
    );
    return {
    read() {
    if (status === "pending") {
    throw suspender;
    } else if (status === "error") {
    throw result;
    } else if (status === "success") {
    return result;
    }
    }
    };
    }

    const fetchUserProfilePromise = (userId) =>
    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then((res) =>
    res.json()
    );

    const SuspensefulUserProfile = ({ resource }) => {
    const data = resource.read();
    return <UserProfile data={data} />;
    };

    const UserProfile = ({ data }) => {
    return (
    <>
    <h1>{data.name}</h1>
    <h2>{data.email}</h2>
    </>
    );
    };

    const UserProfileList = () => (
    <>
    <Suspense fallback={<h1>Loading ...</h1>}>
    <ErrorBoundary fallback={<h2>Could not fetch user profile.</h2>}>
    <SuspensefulUserProfile resource={fetchProfileData(1)} />
    <SuspensefulUserProfile resource={fetchProfileData(2)} />
    <SuspensefulUserProfile resource={fetchProfileData(3)} />
    </ErrorBoundary>
    </Suspense>
    </>
    );

    export default UserProfileList;