Last active
December 1, 2021 17:49
-
-
Save rockchalkwushock/eddafbe866ea0befe2705cd6216f6714 to your computer and use it in GitHub Desktop.
Contra code challenge
This file contains 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 * as React from "react"; | |
interface Props { | |
fallback: React.ReactElement; | |
} | |
interface State { | |
error: null | string; | |
hasError: boolean; | |
} | |
class ErrorBoundary extends React.Component<Props, State> { | |
state = { error: null, hasError: false }; | |
static getDerivedStateFromError(error) { | |
return { | |
hasError: true, | |
error | |
}; | |
} | |
render() { | |
return this.state.hasError ? this.props.fallback : this.props.children; | |
} | |
} | |
export default ErrorBoundary; |
This file contains 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 axios from 'axios' | |
type Status = 'error' | 'pending' | 'success' | |
export const fetchUserProfileById = (id: number) => { | |
// Start in a pending or loadign state | |
let status: Status = 'pending' | |
let result | |
// Axios returns as a promise so capture the promise for use in the | |
// 'pending' state below | |
let suspender = axios(`https://some-api.com/users/${id}`).then( | |
(r) => { | |
// if the promise resolves set the data on 'result' and the correct 'status' | |
result = r.data; | |
status = 'success'; | |
} | |
(e) => { | |
// if the promise rejects set the errro on 'result' and the correct 'status' | |
result = e; | |
status = 'error'; | |
} | |
) | |
// The code above is asynchronous so it will evaluate when it evaluates we are not | |
// waiting on it. The results of the async call will be seen when we call the below | |
// read/0 function that will return based on the current 'status' of the request. | |
return { | |
read() { | |
if (status === 'pending') { | |
throw suspender | |
} else if (status === 'error') { | |
throw result | |
} else if (status === 'success') { | |
return result | |
} | |
} | |
} | |
} |
This file contains 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, useState, useEffect } from 'react'; | |
// Problem #1 | |
// We are not using React.lazy | |
// React.lazy allows for us to lazily load in a component (i.e. code splitting). | |
// This feature coupled with Suspense allows us to not load in what could possibly be | |
// a very heavy component into the UI/UX until the data fetching has resolved and if | |
// it does not resolve we never burdened the user's experience with that heavy component | |
// that never rendered | |
const SuspensefulUserProfile = ({ userId }) => { | |
const [data, setData] = useState({}); | |
useEffect(() => { | |
fetchUserProfile(userId).then((profile) => setData(profile)); | |
}, [userId, setData]) | |
return ( | |
// Problem #2 | |
// Is that we are not utilizing the "fallback" prop which is required. | |
// "fallback" is where we can provide a component that will render while the data | |
// fetching is occuring. This presents the user with some kind of UI/UX to let them | |
// know we are working on getting them their data instead of a dreaded blank screen. | |
// Problem #3 | |
// In the same respect the lack of an ErrorBoundary is a problem. Suspense will render | |
// our fallback component as data fetching is occuring (i.e. loading state), but it will | |
// continue to show that UI/UX even if the data fetching has failed (i.e. error state). | |
// This will leave the user wating and waiting for a request to finish that actually has | |
// we just aren't acting on it. | |
// Adding an ErrorBoundary will allow us to have a secondary fallback that is specifically | |
// for the error state. | |
<Suspense> | |
// Problem #4 | |
// The asynchronous data fetching is happening outside of the scope of Suspense, so technically | |
// there is no reason to be using Suspense here at all. The asynchronous data fetching needs to happen | |
// in the child, or at the very least as per my code example, having the results of that asynchronous call | |
// being made in the child. This way Suspense will handle the rendering based on the state of the data fetching. | |
<UserProfile data={data} /> | |
</Suspense> | |
); | |
}; | |
const UserProfile = ({ data }) => { | |
return ( | |
<> | |
<h1>{data.name}</h1> | |
<h2>{data.email}</h2> | |
</> | |
); | |
}; | |
const UserProfileList = () => ( | |
<> | |
<SuspensefulUserProfile userId={1} /> | |
<SuspensefulUserProfile userId={2} /> | |
<SuspensefulUserProfile userId={3} /> | |
</> | |
); |
This file contains 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 * as React from 'react' | |
import { ErrorBoundary } from '@components/ErrorBoundary' | |
import { ErrorUI } from '@components/ErrorUI' | |
import { LoadingUI } from '@components/LoadingUI' | |
imoprt { fetchUserProfileById } from '@lib/fetchUserProfileById' | |
interface Props { | |
userId: string | |
} | |
const LazyUserProfile = React.lazy(() => import('@components/UserProfile')) | |
export const SuspensefulUserProfile: React.FC<Props> = ({ userId }) => { | |
// When we make this initial call the asynchronous code is executing | |
// but will not be read until we call the read/0 function. | |
const resource = fetchUserProfileById(userId) | |
return ( | |
<ErrorBoundary fallback={<ErrorUI message="Please try back later." />}> | |
<React.Suspense fallback={<LoadingUI message="Loading..." />}> | |
<LazyUserProfile resource={resource} /> | |
</React.Suspense> | |
</ErrorBoundary> | |
) | |
} |
This file contains 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 * as React from 'react' | |
import { User } from '@types' | |
interface Props { | |
resource: { read(): Pick<User, 'email', 'name'> } | |
} | |
const UserProfile: React.FC<Props> = ({ resource }) => { | |
// We send the resource down into the UserProfile to be read because | |
// we want Suspense to do our data rendering. If in the event of the | |
// asynchronous call taking a long time the fallback for Suspense will | |
// render and should the request fail the ErrorBoundary will run. | |
const user = resource.read() | |
return ( | |
<> | |
<h1>{user.name}</h1> | |
<h2>{user.email}</h2> | |
</> | |
) | |
} |
This file contains 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 * as React from 'react' | |
import { User } from '@types' | |
interface Props { | |
users: Array<Pick<User, 'id'>> | |
} | |
export const UserProfileList: React.FC<Props> = ({ users }) => { | |
return ( | |
<> | |
{users.map(({ id }) => <SuspensefulUserProfile userId={id} />)} | |
</> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment