Last active
January 17, 2025 21:26
-
-
Save jkcorrea/9b87713d049bea7c55b690474581f86b to your computer and use it in GitHub Desktop.
AsyncCard
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 { Card, CardContent, CardFooter, CardHeader } from './card' | |
import { Skeleton } from './skeleton' | |
type AsyncCardChild<T> = ((data: T) => React.ReactNode) | React.ReactNode | |
type AsyncCardProps<T> = Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> & { | |
promise?: Promise<T> | |
children?: AsyncCardChild<T> | AsyncCardChild<T>[] | |
} | |
const AsyncCard = <T,>({ promise, children, ...props }: AsyncCardProps<T>) => { | |
const [result, setResult] = React.useState<T>() | |
const [loading, setLoading] = React.useState(false) | |
React.useEffect(() => { | |
if (!promise) return | |
setLoading(true) | |
promise.then((data) => { | |
setResult(data) | |
setLoading(false) | |
}) | |
}, [promise]) | |
function renderChild(child: AsyncCardChild<T>, index: number) { | |
if (typeof child === 'function') { | |
return loading || !result ? ( | |
<Skeleton className="w-full h-4" /> | |
) : ( | |
<React.Fragment key={index}>{child(result)}</React.Fragment> | |
) | |
} | |
return <React.Fragment key={index}>{child}</React.Fragment> | |
} | |
return ( | |
<Card {...props}> | |
{Array.isArray(children) | |
? children.map((child, index) => renderChild(child, index)) | |
: renderChild(children, 0)} | |
</Card> | |
) | |
} | |
const AsyncCardContent = <T,>({ promise, children, ...props }: AsyncCardProps<T>) => { | |
const [result, setResult] = React.useState<T>() | |
const [loading, setLoading] = React.useState(false) | |
React.useEffect(() => { | |
if (!promise) return | |
setLoading(true) | |
promise.then((data) => { | |
setResult(data) | |
setLoading(false) | |
}) | |
}, [promise]) | |
function renderChild(child: AsyncCardChild<T>, index: number) { | |
if (typeof child === 'function') { | |
return loading || !result ? ( | |
<Skeleton className="w-full h-12" /> | |
) : ( | |
<React.Fragment key={index}>{child(result)}</React.Fragment> | |
) | |
} | |
return <React.Fragment key={index}>{child}</React.Fragment> | |
} | |
return ( | |
<CardContent {...props}> | |
{Array.isArray(children) | |
? children.map((child, index) => renderChild(child, index)) | |
: renderChild(children, 0)} | |
</CardContent> | |
) | |
} | |
const AsyncCardHeader = <T,>({ promise, children, ...props }: AsyncCardProps<T>) => { | |
const [result, setResult] = React.useState<T>() | |
const [loading, setLoading] = React.useState(false) | |
React.useEffect(() => { | |
if (!promise) return | |
setLoading(true) | |
promise.then((data) => { | |
setResult(data) | |
setLoading(false) | |
}) | |
}, [promise]) | |
function renderChild(child: AsyncCardChild<T>, index: number) { | |
if (typeof child === 'function') { | |
return loading || !result ? ( | |
<Skeleton className="w-full h-4" /> | |
) : ( | |
<React.Fragment key={index}>{child(result)}</React.Fragment> | |
) | |
} | |
return <React.Fragment key={index}>{child}</React.Fragment> | |
} | |
return ( | |
<CardHeader {...props}> | |
{Array.isArray(children) | |
? children.map((child, index) => renderChild(child, index)) | |
: renderChild(children, 0)} | |
</CardHeader> | |
) | |
} | |
const AsyncCardFooter = <T,>({ promise, children, ...props }: AsyncCardProps<T>) => { | |
const [result, setResult] = React.useState<T>() | |
const [loading, setLoading] = React.useState(false) | |
React.useEffect(() => { | |
if (!promise) return | |
setLoading(true) | |
promise.then((data) => { | |
setResult(data) | |
setLoading(false) | |
}) | |
}, [promise]) | |
function renderChild(child: AsyncCardChild<T>, index: number) { | |
if (typeof child === 'function') { | |
return loading || !result ? ( | |
<Skeleton className="w-full h-4" /> | |
) : ( | |
<React.Fragment key={index}>{child(result)}</React.Fragment> | |
) | |
} | |
return <React.Fragment key={index}>{child}</React.Fragment> | |
} | |
return ( | |
<CardFooter {...props}> | |
{Array.isArray(children) | |
? children.map((child, index) => renderChild(child, index)) | |
: renderChild(children, 0)} | |
</CardFooter> | |
) | |
} | |
export { AsyncCard, AsyncCardContent, AsyncCardFooter, AsyncCardHeader } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
renders a Skeleton while promise resolves. data arg inside any function children will be typed as the return type of the promise
usage: