Skip to content

Instantly share code, notes, and snippets.

@khanzzirfan
Created September 26, 2021 02:33
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 khanzzirfan/46fe491b3bb52d024453f81ece308b61 to your computer and use it in GitHub Desktop.
Save khanzzirfan/46fe491b3bb52d024453f81ece308b61 to your computer and use it in GitHub Desktop.
/**
* 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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment