Last active
February 18, 2024 10:17
-
-
Save fResult/162a0c72a415654c6dc5542fda3770e4 to your computer and use it in GitHub Desktop.
Higher Order Functions in mapping data fetching Strapi API integrate with React Query
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 { compose, isEmpty, map } from 'lodash/fp'; | |
import { useQuery } from '@tanstack/react-query'; | |
// USAGE | |
// This makes us read "mapStrapiResponse toStaticContent AFTER fetchSomeStaticPageContent" | |
const fetchAndMapContent: () => Promise<SomeStaticContentAttrs> = compose( | |
mapStrapiResponse(toStaticContent), | |
fetchSomeStaticPageContent, | |
); | |
const pageContent: SomeStaticContentAttrs = await fetchAndMapContent(); | |
// OR | |
const { data: pageContent, loading: pageContentLoading } = useQuery({ | |
queryKey: ['some-page-content'], | |
queryFunction: compose(mapStrapiResponse(toStaticContent), fetchSomeStaticPageContent), | |
}); | |
// This makes us read "mapStrapiListResponse toAuthor AFTER fetchAuthors" | |
const fetchAndMapAuthors: () => Promise<Array<AuthorAttrs>> = compose( | |
mapStrapiListResponse(toAuthor), | |
fetchAuthors, | |
); | |
const authors: Array<AuthorAttrs> = await fetchAndMapAuthors(); | |
// OR | |
const { data: authors, loading: authorsLoading } = useQuery({ | |
queryKey: ['authors'], | |
queryFunction: compose(mapStrapiListResponse(toAuthor), fetchAuthors), | |
}); | |
// This makes us read "mapStrapiResponse toAuthor AFTER fetchAuthorById of authorId" | |
const fetchAndMapAuthor: () => Promise<AuthorAttrs> = compose( | |
mapStrapiResponse(toAuthor), | |
fetchAuthorById(new AutherID(authorId)), | |
); | |
const author: AuthorAttrs = await fetchAndMapAuthor(); | |
// OR | |
const { data: author, loading: authorLoading } = useQuery({ | |
queryKey: ['authors' { id: authorId }], | |
queryFunction: compose(mapStrapiListResponse(toAuthor), fetchAuthorById(new AutherID(authorId))), | |
}); | |
class AuthorID { | |
constructor(private readonly _value: number) {} | |
get value(): number { | |
return this._value; | |
} | |
}; | |
// IMPLEMENTATION | |
function toStaticContent(response: StrapiData<SomeStaticContentAttrsResponse>): SomeStaticContentAttrsResponse { | |
if (isEmpty(response)) return {} as SomeStaticContentAttrsResponse; | |
const { authors, ...attrs } = response.attributes; | |
return { | |
id: response.id, | |
...attrs, | |
authors: map(toAuthor)(authors.data), | |
}; | |
} | |
function toAuthor(response: StrapiData<AuthorAttrsResponse>): AuthorAttrs { | |
if (isEmpty(response)) return {} as AuthorAttrs; | |
return { | |
id: response.id, | |
...response.attributes, | |
}; | |
} | |
type SomeStaticContentAttrsResponse = BaseStrapiAttrs & { | |
title: string; | |
subTitle: string; | |
shortContent: string; | |
/** HTML String */ | |
fullContent: string; | |
authors: StrapiResponse<AuthorAttrsResponse>; | |
} | |
enum Position { Developer, Designer, ProductOwner } | |
type AuthorAttrsResponse = BaseStrapiAttrs & { | |
name: string; | |
position: Position; | |
aboutMe: string; | |
} | |
type SomeStaticContentAttrs = BaseStrapiAttrs & { | |
id: number; | |
title: string; | |
subTitle: string; | |
shortContent: string; | |
/** HTML String */ | |
fullContent: string; | |
authors: Array<AuthorAttrs>; | |
} | |
type AuthorAttrs = BaseStrapiAttrs & { | |
id: number; | |
name: string; | |
position: Position; | |
aboutMe: string; | |
} | |
// IMPLEMENTATION AS HIGHER ORDER FUNCTIONS | |
type Mapper<T, R> = (x: T) => R; | |
function mapStrapiResponse<T extends StrapiResponse<BaseStrapiAttrs>, R extends BaseStrapiAttrs>( | |
toContent: Mapper<T['data'], R>, | |
): (fResponse: Promise<T>) => Promise<R> { | |
return async function forFutureResponse(fResponse) { | |
const response = await fResponse; | |
if (isEmpty(response)) return {} as R; | |
return toContent(response.data); | |
}; | |
} | |
function mapStrapiListResponse<T extends StrapiResponse<Array<BaseStrapiAttrs>>, R extends BaseStrapiAttrs>( | |
toContent: Mapper<T['data'][number], R>, | |
): (fResponse: Promise<T>) => Promise<Array<R>> { | |
return async function forFutureResponse(fResponse) { | |
const response = await fResponse; | |
if (isEmpty(response)) return {} as Array<R>; | |
return map(toContent)(response.data); | |
}; | |
} | |
function mapPaginateStrapiResponse<T extends StrapiResponse<Array<BaseStrapiAttrs>>, R extends BaseStrapiAttrs>( | |
toContent: Mapper<T['data'][number], R>, | |
): (fResponse: Promise<T>) => Promise<{ data: Array<R>; meta: StrapiMeta }> { | |
return async function forFutureResponse(fResponse) { | |
const response = await fResponse; | |
type ListData = StrapiResponse<Array<R>>['data']; | |
type Meta = StrapiResponse<Array<R>>['meta']; | |
if (isEmpty(response)) return { data: [] as Array<R>, meta: {} as Meta }; | |
const { data = [] as ListData, meta = {} as Meta } = response; | |
return { | |
data: map(toContent)(data), | |
meta, | |
}; | |
}; | |
} | |
type BaseStrapiAttrs = { | |
locale: StrapiLocale; | |
localizations: StrapiLocalization; | |
/** Date String */ | |
createdAt: string; | |
/** Date String */ | |
updatedAt: string; | |
/** Date String */ | |
publishedAt: string; | |
}; | |
type StrapiData<Attrs> = { | |
id: number; | |
attributes: Attrs; | |
}; | |
type StrapiResponse<Attrs> = Attrs extends Array<infer Element> | |
? { | |
data: Array<StrapiData<Element>>; | |
meta: StrapiMeta; | |
} | |
: Attrs extends BaseStrapiAttrs | |
? { | |
data: StrapiData<Attrs>; | |
// TODO : for now we don't know the meta content will be sent from strapi, if it's single type. | |
meta: StrapiMeta; | |
} | |
: Attrs; | |
type StrapiMeta = { | |
pagination: { | |
page: number; | |
pageSize: number; | |
pageCount: number; | |
total: number; | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment