Skip to content

Commit

Permalink
[C-2598] Add types for audius-query hooks (#3406)
Browse files Browse the repository at this point in the history
  • Loading branch information
amendelsohn authored May 23, 2023
1 parent 8bf784a commit 67b1788
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 67 deletions.
14 changes: 6 additions & 8 deletions packages/common/src/api/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ const collectionApi = createApi({
endpoints: {
getPlaylistByPermalink: {
fetch: async ({ permalink, currentUserId }, { apiClient }) => {
return {
collection: (
await apiClient.getPlaylistByPermalink({
permalink,
currentUserId
})
)[0]
}
return (
await apiClient.getPlaylistByPermalink({
permalink,
currentUserId
})
)[0]
},
options: {
permalinkArgKey: 'permalink',
Expand Down
53 changes: 37 additions & 16 deletions packages/common/src/api/createApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ import { apiResponseSchema } from './schema'
import {
Api,
ApiState,
CreateApiConfig,
DefaultEndpointDefinitions,
EndpointConfig,
FetchErrorAction,
FetchLoadingAction,
FetchSucceededAction,
QueryHookOptions,
PerEndpointState,
PerKeyState,
SliceConfig
SliceConfig,
QueryHookResults
} from './types'
import {
capitalize,
Expand All @@ -38,11 +39,19 @@ import {
} from './utils'
const { addEntries } = cacheActions

export const createApi = ({ reducerPath, endpoints }: CreateApiConfig) => {
export const createApi = <
EndpointDefinitions extends DefaultEndpointDefinitions
>({
reducerPath,
endpoints
}: {
reducerPath: string
endpoints: EndpointDefinitions
}) => {
const api = {
reducerPath,
hooks: {}
} as unknown as Api
} as unknown as Api<EndpointDefinitions>

const sliceConfig: SliceConfig = {
name: reducerPath,
Expand All @@ -65,8 +74,11 @@ export const createApi = ({ reducerPath, endpoints }: CreateApiConfig) => {
return api
}

const addEndpointToSlice = (sliceConfig: SliceConfig, endpointName: string) => {
const initState: PerKeyState = {
const addEndpointToSlice = <NormalizedData>(
sliceConfig: SliceConfig,
endpointName: string
) => {
const initState: PerKeyState<NormalizedData> = {
status: Status.IDLE
}
sliceConfig.initialState[endpointName] = {}
Expand Down Expand Up @@ -108,24 +120,31 @@ const addEndpointToSlice = (sliceConfig: SliceConfig, endpointName: string) => {
}
}

const buildEndpointHooks = (
api: Api,
const buildEndpointHooks = <
EndpointDefinitions extends DefaultEndpointDefinitions,
Args,
Data
>(
api: Api<EndpointDefinitions>,
endpointName: string,
endpoint: EndpointConfig,
endpoint: EndpointConfig<Args, Data>,
actions: CaseReducerActions<any>,
reducerPath: string
) => {
// Hook to be returned as use<EndpointName>
const useQuery = (fetchArgs: any, hookOptions?: QueryHookOptions) => {
const useQuery = (
fetchArgs: Args,
hookOptions?: QueryHookOptions
): QueryHookResults<Data> => {
const dispatch = useDispatch()
const key = getKeyFromFetchArgs(fetchArgs)
const queryState = useSelector((state: any) => {
const queryState = useSelector((state: CommonState) => {
if (!state.api[reducerPath]) {
throw new Error(
`State for ${reducerPath} is undefined - did you forget to register the reducer in @audius/common/src/api/reducers.ts?`
)
}
const endpointState: PerEndpointState =
const endpointState: PerEndpointState<any> =
state.api[reducerPath][endpointName]

// Retrieve data from cache if lookup args provided
Expand Down Expand Up @@ -193,12 +212,11 @@ const buildEndpointHooks = (
isInitialValue
} = queryState ?? {
nonNormalizedData: null,
status: Status.IDLE,
errorMessage: null
status: Status.IDLE
}

// Rehydrate local nonNormalizedData using entities from global normalized cache
let cachedData = useSelector((state: CommonState) => {
let cachedData: Data = useSelector((state: CommonState) => {
const rehydratedEntityMap =
strippedEntityMap && selectRehydrateEntityMap(state, strippedEntityMap)
return rehydratedEntityMap
Expand Down Expand Up @@ -237,7 +255,10 @@ const buildEndpointHooks = (
throw new Error('Remote data not found')
}

const { entities, result } = normalize(apiData, apiResponseSchema)
const { entities, result } = normalize(
{ [endpoint.options.schemaKey]: apiData },
apiResponseSchema
)
dispatch(addEntries(Object.keys(entities), entities))
const strippedEntityMap = stripEntityMap(entities)

Expand Down
7 changes: 3 additions & 4 deletions packages/common/src/api/relatedArtists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ const relatedArtistsApi = createApi({
reducerPath: 'relatedArtistsApi',
endpoints: {
getRelatedArtists: {
fetch: async ({ artistId }, { apiClient }) => ({
users: await apiClient.getRelatedArtists({
fetch: async ({ artistId }, { apiClient }) =>
await apiClient.getRelatedArtists({
userId: artistId,
limit: 50
})
}),
}),
options: {
schemaKey: 'users'
}
Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { schema } from 'normalizr'

import { Kind } from 'models/Kind'

export type SchemaKey = 'user' | 'users' | 'track' | 'tracks'

export const userSchema = new schema.Entity(
Kind.USERS,
{},
Expand Down
18 changes: 7 additions & 11 deletions packages/common/src/api/track.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ const trackApi = createApi({
reducerPath: 'trackApi',
endpoints: {
getTrackById: {
fetch: async ({ id }, { apiClient }) => {
return {
track: await apiClient.getTrack({ id })
}
fetch: async ({ id }: { id: number }, { apiClient }) => {
return await apiClient.getTrack({ id })
},
options: {
idArgKey: 'id',
Expand All @@ -21,13 +19,11 @@ const trackApi = createApi({
getTrackByPermalink: {
fetch: async ({ permalink, currentUserId }, { apiClient }) => {
const { handle, slug } = parseTrackRouteFromPermalink(permalink)
return {
track: await apiClient.getTrackByHandleAndSlug({
handle,
slug,
currentUserId
})
}
return await apiClient.getTrackByHandleAndSlug({
handle,
slug,
currentUserId
})
},
options: {
permalinkArgKey: 'permalink',
Expand Down
55 changes: 31 additions & 24 deletions packages/common/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,40 @@ import { Kind, Status } from 'models'

import { AudiusQueryContextType } from './AudiusQueryContext'

export type Api = {
export type DefaultEndpointDefinitions = {
[key: string]: EndpointConfig<any, any>
}

export type Api<EndpointDefinitions extends DefaultEndpointDefinitions> = {
reducer: Reducer<any, Action>
hooks: {
[key: string]: (...fetchArgs: any[]) => any
[Property in keyof EndpointDefinitions as `use${Capitalize<
string & Property
>}`]: (
fetchArgs: Parameters<EndpointDefinitions[Property]['fetch']>[0]
) => QueryHookResults<
Awaited<ReturnType<EndpointDefinitions[Property]['fetch']>>
>
}
}

export type CreateApiConfig = {
reducerPath: string
endpoints: { [name: string]: EndpointConfig<any, any> }
}

export type SliceConfig = CreateSliceOptions<any, any, any>

type EndpointOptions = {
idArgKey?: string
permalinkArgKey?: string
schemaKey?: string
schemaKey: string
kind?: Kind
}

export type EndpointConfig = {
fetch: (fetchArgs: any, context: AudiusQueryContextType) => Promise<any>
options?: EndpointOptions
export type EndpointConfig<Args, Data> = {
fetch: (fetchArgs: Args, context: AudiusQueryContextType) => Promise<Data>
options: EndpointOptions
}

export type EntityMap = {
Expand All @@ -42,7 +57,7 @@ export type StrippedEntityMap = {
}

type FetchBaseAction = {
fetchArgs: any[]
fetchArgs: any
}
export type FetchLoadingAction = PayloadAction<FetchBaseAction & {}>
export type FetchErrorAction = PayloadAction<
Expand All @@ -52,38 +67,30 @@ export type FetchErrorAction = PayloadAction<
>
export type FetchSucceededAction = PayloadAction<
FetchBaseAction & {
id: any
nonNormalizedData: any
strippedEntityMap: StrippedEntityMap
}
>

export type PerKeyState = {
export type ApiState = {
[key: string]: PerEndpointState<any>
}
export type PerEndpointState<NormalizedData> = {
[key: string]: PerKeyState<NormalizedData>
}
export type PerKeyState<NormalizedData> = {
status: Status
nonNormalizedData?: any
nonNormalizedData?: NormalizedData
strippedEntityMap?: StrippedEntityMap
errorMessage?: string
}

export type PerEndpointState = {
[key: string]: PerKeyState
}

export type ApiState = {
[key: string]: PerEndpointState
}

export type CreateApiConfig = {
reducerPath: string
endpoints: { [name: string]: EndpointConfig }
}

export type QueryHookOptions = {
disabled?: boolean
}

export type QueryHookResults<Data> = {
data: Data
status: Status
errorMessage: string
errorMessage?: string
}
4 changes: 1 addition & 3 deletions packages/common/src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ const userApi = createApi({
getUserById: {
fetch: async ({ id, currentUserId }, { apiClient }) => {
const apiUser = await apiClient.getUser({ userId: id, currentUserId })
return {
user: apiUser?.[0]
}
return apiUser?.[0]
},
options: {
idArgKey: 'id',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback } from 'react'

import type { User } from '@audius/common'
import {
profilePageSelectors,
MAX_PROFILE_RELATED_ARTISTS,
Expand Down Expand Up @@ -69,7 +70,7 @@ export const RelatedArtists = () => {
/>
<ProfilePictureListTile
onClick={handleClick}
users={relatedArtists}
users={relatedArtists as User[]}
totalUserCount={relatedArtists.length}
limit={MAX_PROFILE_RELATED_ARTISTS}
disableProfileClick
Expand Down

0 comments on commit 67b1788

Please sign in to comment.