Skip to content

Instantly share code, notes, and snippets.

@antoniel
Last active September 29, 2021 00:09
Show Gist options
  • Save antoniel/9228adceacdc7cbb79d32fc70466fd9d to your computer and use it in GitHub Desktop.
Save antoniel/9228adceacdc7cbb79d32fc70466fd9d to your computer and use it in GitHub Desktop.
import { Suspense } from "react";
import graphql from "babel-plugin-relay/macro";
import {
RelayEnvironmentProvider,
loadQuery,
usePreloadedQuery,
} from "react-relay/hooks";
import { ErrorBoundary } from "react-error-boundary";
import RelayEnvironment from "./RelayEnvironment";
const UserQuery = graphql`
query appQuery($userId: String) {
user(id: $userId) {
...TodoApp_user
}
}
`;
/**
* Identified issues:
* 1. Fetching data out of the Suspense component on `SuspensefulUserProfile` instead inside `UserProfile`
* 2. Using a fetch-as-render approach instead fetch-then-render by using useEffect.
* 3. Missing ErrorBoundary on react-tree, so if the fetch throws an error it will break the page causing a bad UX.
*/
const SuspensefulUserProfile = ({ userId }) => {
/** Preload the query based on the UserId */
const preloadedUserQuery = loadQuery(RelayEnvironment, UserQuery, {
userId,
});
const wrappedData = wrapPromise(fetchUserProfile(userId));
/** Move data fetch to one level down in react tree */
/** Adds a ErrorBoundary in case the fetch throw a error */
return (
<ErrorBoundary FallbackComponent={PlaceholderError}>
{/** Add a fallback to presents some ui to the user while fetching data */}
<Suspense fallback={<Placeholder />}>
<UserProfile
preloadedUserQuery={preloadedUserQuery}
wrappedData={wrappedData}
/>
</Suspense>
</ErrorBoundary>
);
};
/**
* With these two implementations data can be replaced by dataFromFetch
*/
const UserProfile = ({ preloadedUserQuery, wrappedData }) => {
/**
* This is the current implementation of render as fetch-then-render using relay
* the query is made by loadQuery on SuspensefulUserProfile and suspenses while is not
* resolved by usePreloadedQuery.
*/
const data = usePreloadedQuery(UserQuery, preloadedUserQuery);
/**
* The implementation below without the use of relay
* also will suspend the component while the promise is not resolved.
* The previous implementation with fetch-as-render didn't dispatch the suspender.
*/
const dataFromFetch = wrappedData.read();
return (
<>
<h1>{data.name}</h1>
<h2>{data.email}</h2>
</>
);
};
const UserProfileList = () => (
<RelayEnvironmentProvider environment={RelayEnvironment}>
<SuspensefulUserProfile userId={1} />
<SuspensefulUserProfile userId={2} />
<SuspensefulUserProfile userId={3} />
</RelayEnvironmentProvider>
);
const Placeholder = () => <h1>Loading user profile</h1>;
const PlaceholderError = () => <h1>Something goes wrong</h1>;
/**
* WrapPromise is a HOC that will embrace a promise
* implementing the contract of a suspender in a simplified way
* Throwing the `then` method of the promises this exception will
* be treated by `react scheduler` that will mount the next component
* until the promises is pending
*/
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;
}
},
};
}
import {Environment, Network, RecordSource, Store} from 'relay-runtime';
async function fetchGraphQL(text, variables) {
const REACT_APP_AUTH_TOKEN = process.env.REACT_APP_AUTH_TOKEN;
const response = await fetch('https://api.com/graphql', {
method: 'POST',
headers: {
Authorization: `bearer ${REACT_APP_AUTH_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: text,
variables,
}),
});
return await response.json();
}
async function fetchRelay(params, variables) {
console.log(`fetching query ${params.name} with ${JSON.stringify(variables)}`);
return fetchGraphQL(params.text, variables);
}
export default new Environment({
network: Network.create(fetchRelay),
store: new Store(new RecordSource()),
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment