Skip to content

Instantly share code, notes, and snippets.

@SalahHamza
Last active January 15, 2022 18:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SalahHamza/cdf840c19669952e62451de5355726a9 to your computer and use it in GitHub Desktop.
Save SalahHamza/cdf840c19669952e62451de5355726a9 to your computer and use it in GitHub Desktop.
Concurrent Mode Suspense example
/**
* The issues I found are the following:
* 1) No 'fallback', the benefit of suspense is to render as early as possible.
* 2) The component fetching the data should "suspend", fetching user profile in a useEffect
* Doesn't do that.
* 3) The suspense should wrap the componenet fetching the data (i.e. SuspensefulUserProfile).
*
* Extra notes:
* - Making a request for each profile separately is definitely something to be avoided,
* it would be much better to fetch the profiles together.
* - No error handling. The way to handle errors in this case is by adding an ErrorBounary.
* - Relay should be used in this case since Suspense on it's own is not ready, yet.
*/
import { Suspense, Component } from "react";
import { fetchProfileData } from "./fetchUser"
const SuspensefulUserProfile = ({ userId }) => {
const data = fetchProfileData(userId).read();
return <UserProfile data={data} />;
};
const UserProfile = ({ data }) => {
return (
<>
<h1>{data.name}</h1>
<h2>{data.email}</h2>
</>
);
};
const UserProfileList = () => (
<>
<ErrorBoundary fallback={<UserProfileError />}>
<Suspense fallback={<UserProfileLoading />}>
<SuspensefulUserProfile userId={1} />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<UserProfileError />}>
<Suspense fallback={<UserProfileLoading />}>
<SuspensefulUserProfile userId={2} />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<UserProfileError />}>
<Suspense fallback={<UserProfileLoading />}>
<SuspensefulUserProfile userId={3} />
</Suspense>
</ErrorBoundary>
</>
);
class ErrorBoundary extends 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;
}
}
const UserProfileLoading = () => {
return <div>Loading user profile...</div>;
};
const UserProfileError = () => {
return <div>Failed to fetch user list</div>;
};
const users = {
1: {
name: "John",
email: "John@example.com"
},
2: {
name: "eric",
email: "eric@example.com"
},
3: {
name: "hamza",
email: "hamza@example.com"
}
};
/**
* Api call to fetch user profile
*/
const fetchUserProfile = (userId) => {
if (users[userId]) {
return Promise.resolve(() => users[userId]);
}
return Promise.reject(() => new Error("User profile not found."));
};
/**
* Suspending api call to fetch user profile
*/
const fetchUserProfileData = (userId) => {
return wrapPromise(fetchUserProfile(userId));
};
// Suspense integrations like Relay implement
// a contract like this to integrate with React.
// This code is copied from react suspense docs (not intended for prod).
// check here: https://codesandbox.io/s/frosty-hermann-bztrp?file=/src/index.js
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;
}
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment