Skip to content

Instantly share code, notes, and snippets.

@acro5piano
Created October 27, 2018 05:56
Show Gist options
  • Select an option

  • Save acro5piano/4f1b9d9ca4155153d8519bd34c9007ba to your computer and use it in GitHub Desktop.

Select an option

Save acro5piano/4f1b9d9ca4155153d8519bd34c9007ba to your computer and use it in GitHub Desktop.
fetch data HOC with strongly typed
import * as React from 'react'
import { get, WithData } from './decorator'
import * as moment from 'moment'
interface PostResponse {
id: number
userId: number
title: string
body: string
}
interface Post extends PostResponse {
truncatedBody: string
now: moment.Moment
}
interface AppProps {
name: string
}
type Props = WithData<Post[]> & AppProps
class App extends React.Component<Props> {
render() {
const { data } = this.props
return (
<div>
{data.map(post => (
<div key={post.id}>
<b>{post.title}</b>
<p>{post.truncatedBody}</p>
<p>{post.now.format('YYYY-MM-DD')}</p>
</div>
))}
</div>
)
}
}
const WithGet = get<Props, PostResponse[]>('https://jsonplaceholder.typicode.com/posts', {
serializer: posts =>
posts.map(post => ({
...post,
truncatedBody: `${post.body.slice(0, 20)}...`,
now: moment(),
})),
})(App)
export const S = () => <WithGet name="hoge" />
export default S
import * as React from 'react'
interface State<T> {
data?: T
loading?: boolean
}
export interface WithData<T> {
data: T
loading?: boolean
}
interface Options<Response, SerializedReponse> {
delayMount?: boolean
serializer?: (data: Response) => SerializedReponse
}
const defaultOptions = {
delayMount: true,
serializer: undefined,
}
// here is the magic - omitting an attribute
type Keys = string | number | symbol
type Diff<T extends Keys, U extends Keys> = ({ [P in T]: P } &
{ [P in U]: never } & { [x: string]: never })[T]
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>
// end of magic
export const get = <
P extends WithRealityProps<SerializedReponse>,
Response = any,
SerializedReponse = P['data']
>(
url: string,
options: Options<Response, SerializedReponse> = defaultOptions,
) => (ComposedComponent: React.ComponentType<P>) =>
class Gettable extends React.Component<
Omit<P, keyof WithRealityProps<SerializedReponse>>,
State<SerializedReponse>
> {
options: Options<Response, SerializedReponse> = { ...defaultOptions, ...options }
state = {
data: undefined,
loading: true,
}
async componentDidMount() {
let data = await fetch(url).then(res => res.json())
if (this.options.serializer) {
data = this.options.serializer(data)
}
this.setState(() => ({ data, loading: false }))
}
render() {
const { loading, data } = this.state
if ((this.options.delayMount && loading) || !data) {
return <React.Fragment />
}
return (
<ComposedComponent data={data as SerializedReponse} loading={loading} {...this.props} />
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment