Consider the following code:
const defaultState = () => {
return {
profile: {
id: '',
displayName: '',
givenName: '',
},
photo: '',
}
}
const state = reactive(defaultState())
export const setGraphProfile = async () => {
const response = await getGraphProfile()
state.profile = { ...defaultState().profile, ...response.data }
}
Which generates the ESLint warning:
@typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value.
This means that the properties in response.data
might not match the ones of the profile
. The return of getGraphProfile
is Promise<AxiosResponse<any>>
. Of course it's easy to get rid of this ESLint warning by simply ignoring it:
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
state.profile = { ...defaultState().profile, ...response.data }
getGraphProfile
so it does match?
Because one can create a TS interface
but that would simply create duplicate code with the object defaultState().profile
const callGraph = (
url: string,
token: string,
axiosConfig?: AxiosRequestConfig
) => {
const params: AxiosRequestConfig = {
method: 'GET',
url: url,
headers: { Authorization: `Bearer ${token}` },
}
return axios({ ...params, ...axiosConfig })
}
const getGraphDetails = async (
uri: string,
scopes: string[],
axiosConfig?: AxiosRequestConfig
) => {
try {
const response = await getToken(scopes)
if (response && response.accessToken) {
return callGraph(uri, response.accessToken, axiosConfig)
} else {
throw new Error('We could not get a token because of page redirect')
}
} catch (error) {
throw new Error(`We could not get a token: ${error}`)
}
}
export const getGraphProfile = async () => {
try {
return await getGraphDetails(
config.resources.msGraphProfile.uri,
config.resources.msGraphProfile.scopes
)
} catch (error) {
throw new Error(`Failed retrieving the graph profile: ${error}`)
}
}
export const getGraphPhoto = async () => {
try {
const response = await getGraphDetails(
config.resources.msGraphPhoto.uri,
config.resources.msGraphPhoto.scopes,
{ responseType: 'arraybuffer' }
)
if (!(response && response.data)) {
return ''
}
const imageBase64 = new Buffer(response.data, 'binary').toString('base64')
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return `data:${response.headers['content-type']};base64, ${imageBase64}`
} catch (error) {
throw new Error(`Failed retrieving the graph photo: ${error}`)
}
}
TypeScript doesn't generate warnings, only errors. As far as TS is concerned, that any
assignment is valid. This is where the linter comes in to offer additional support.
Luckily you don't need to duplicate your interface. Use TypeScript's ReturnType
to get the type of the profile
object in your defaultState
method:
type IProfile = ReturnType<typeof defaultState>["profile"]
The above line utilizes 3 great TypeScript features:
ReturnType
to infer the type that a function returnstypeof
to infer the interface from an object instance["profile"]
to get the type of a certain property of an interfaceNow, make your callGraph
function generic:
function callGraph<T>(url: string, token: string, axiosConfig?: AxiosRequestConfig) {
const params: AxiosRequestConfig = {
method: 'GET',
url: url,
headers: { Authorization: `Bearer ${token}` },
}
return axios.request<T>({ ...params, ...axiosConfig })
}
And update the callGraph
call in your getGraphDetails
function:
...
if (response && response.accessToken) {
return callGraph<IProfile>(uri, response.accessToken, axiosConfig)
}
...
Now your graph calls are properly typed, and you didn't have to duplicate your profile definition; rather you used TypeScript's awesome type inference technique to "read your interface" from the return type of your function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With