Skip to content

Instantly share code, notes, and snippets.

@ArrayKnight
Forked from Boeing787/ContraTest.tsx
Last active October 12, 2020 17:37
Show Gist options
  • Save ArrayKnight/930385aed4a1d46be20560e1f83e5408 to your computer and use it in GitHub Desktop.
Save ArrayKnight/930385aed4a1d46be20560e1f83e5408 to your computer and use it in GitHub Desktop.
import React, { Component, memo, ReactElement, Suspense, useState, useEffect } from 'react';
import { fetchUserProfile } from '../elsewhere'
enum Status {
Pending,
Success,
Failure,
}
interface Suspended<T extends unknown> {
read(): T
}
interface User {
name: string
email: string
}
function wrapPromise<T extends unknown>(promise: Promise<T>): Suspended<T> {
let status = Status.Pending;
let result;
let suspender = promise.then(
(r) => {
status = Status.Success;
result = r;
},
(e) => {
status = Status.Failure;
result = e;
}
);
return {
read() {
switch (status) {
case Status.Pending:
throw suspender;
case Status.Failure:
throw result;
case Status.Success:
return result;
}
}
};
}
function fetchUserProfiles(userIds: number[]): Record<number, Suspended<User>> {
return userIds.reduce((requests, userId) => ({
...requests,
[userId]: wrapPromise(fetchUserProfile(userId))
}), {});
}
const Fallback = memo((message: string): ReactElement => <div>{message}</div>)
interface ErrorBoundaryProps {
children: ReactElement
fallback: ReactElement
}
interface ErrorBoundaryState {
hasError: boolean
error: Error | null
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
public static getDerivedStateFromError(error) {
return {
hasError: true,
error
};
}
public state = { hasError: false, error: null };
public render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
interface UserProfileProps {
request: Suspended<User>
}
const UserProfile = ({ request }: UserProfileProps): ReactElement => {
const { name, email } = request.read();
return (
<>
<h1>{name}</h1>
<h2>{email}</h2>
</>
);
};
interface SuspensefulUserProfileProps {
userId: number
request: Suspended<User>
}
const SuspensefulUserProfile = ({ userId, request }: SuspensefulUserProfileProps): ReactElement => (
<ErrorBoundary fallback={<Fallback message={`Failed to load user profile #${userId}`} />}>
<Suspense fallback={<Fallback message="Loading..." />}>
<UserProfile request={request} />
</Suspense>
</ErrorBoundary>
);
const initialUserIds = [1, 2, 3]
const initialResource = fetchUserProfiles(initialUserIds)
const UserProfileList = (): ReactElement => {
const [userIds] = useState(initialUserIds) // setUserIds unused for now
const [resource, setResource] = useState(initialResource)
useEffect(() => {
setResource(fetchUserProfiles(userIds))
}, [...userIds])
return (
<>
{Object
.entries(resource)
.map(([userId, request]) => (
<SuspensefulUserProfile
key={userId}
userId={userId}
request={request} />
))}
</>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment