Last active
May 21, 2021 09:06
-
-
Save troygoode/14103bcdb98ecc476c4c3c47ab8c3182 to your computer and use it in GitHub Desktop.
type-safe Fetch & Re-Fetch with Hooks & TypeScript
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 React, { useState } from 'react' | |
import { getJSON } from './fetch-json' | |
import useRefreshablePromise from './use-refreshable-promise' | |
import Async from './async' | |
interface IProfile { | |
id: number | |
name: string | |
} | |
const fetchProfiles = async () => { | |
return getJSON<IProfile[]>('/profiles') | |
} | |
const createProfile = async () => { | |
// TODO: implement | |
return | |
} | |
export default () => { | |
const [profiles, setProfiles] = useState<IProfile[]>([]) | |
const { refresh, ...fetchState } = useRefreshablePromise(fetchProfiles, setProfiles) | |
const onCreateProfileClick = async () { | |
await createProfile() | |
await refresh() // re-execute fetchProfiles and re-render component | |
} | |
return ( | |
<div> | |
<Async state={fetchState}> | |
<ul> | |
{profiles.map((profile) => ( | |
<li key={profile.id}>{profile.name}</li> | |
)} | |
</ul> | |
</Async> | |
<button onClick={onCreateProfileClick}>Create New Profile</button> | |
</div> | |
) | |
} |
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 React from 'react' | |
interface IState { | |
isLoading: boolean | |
error?: Error | |
} | |
interface IErrorProps { | |
error: Error | |
} | |
interface IAsyncProps { | |
state: IState | |
renderError?: React.Component<IErrorProps> | |
renderLoading?: React.Component<any> | |
children: React.Component<any> | |
} | |
const DefaultErrorComponent = ({ error }: IErrorProps) => { | |
return <p>ERROR! {error.toString()}</p> | |
} | |
const DefaultLoadingComponent = () => { | |
return <p>Loading...</p> | |
} | |
export default ({ state, renderError: ErrorComponent, renderLoading: LoadingComponent, children }: IAsyncProps) => { | |
const ErrorOverride = ErrorComponent || DefaultErrorComponent | |
const LoadingOverride = LoadingComponent || DefaultLoadingComponent | |
if (state.error) { | |
return <ErrorOverride error={state.error} /> | |
} else if (state.isLoading) { | |
return <LoadingOverride /> | |
} else { | |
return children | |
} | |
} |
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
// using native browser fetch; just wrapping for clarity | |
export async function getJSON<T> (url): T { | |
return (fetch(url).then(res => res.json()) as unknown) as T | |
} |
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 { useMemo } from 'react' | |
import { usePromise } from 'react-hook-utils' | |
export default <T>(fetchResult: () => Promise<T>, setResult: (result: T) => any) => { | |
const refresh = async () => { | |
return fetchResult() | |
.then((result) => { | |
setResult(result) | |
}) | |
} | |
const fetchMemo = useMemo(refresh, []) | |
const [, error, isLoading] = usePromise(fetchMemo) | |
return { | |
isLoading, | |
error, | |
refresh | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment