Last active
September 29, 2021 00:09
-
-
Save antoniel/9228adceacdc7cbb79d32fc70466fd9d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
}, | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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