Avoid insecurely assigning an any value in TypeScript

Take a look at this code snippet:

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 }
}

An ESLint warning is triggered by this code:

@typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value.

This warning indicates that the properties in response.data may not match those of the profile. The return type of getGraphProfile is

Promise<AxiosResponse<any>>
. To suppress the ESLint warning, you can simply disable it like this:

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
state.profile = { ...defaultState().profile, ...response.data }

Queries:

  • How can we structure the data in the Promise getGraphProfile to ensure compatibility? One option is to create a TS interface, but this would result in redundant code with the object defaultState().profile
  • Why does TypeScript not raise an issue with this code while the linter does? Shouldn't they both be aligned?

The implementations:

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}`)
  }
}

Answer №1

TypeScript doesn't output warnings, only errors. According to TS, the assignment of 'any' is considered valid. However, a linter can provide additional support in this area.

No need to replicate your interface. Utilize TypeScript's 'ReturnType' to determine the type of the 'profile' object in your 'defaultState' method:

type IProfile = ReturnType<typeof defaultState>["profile"]

The code above showcases three outstanding TypeScript features:

  • 'ReturnType' for inferring function return types
  • 'typeof' for deducing interfaces from object instances
  • Using '["profile"]' to identify the type of a specific property within an interface

Make your 'callGraph' function generic now:

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 })
}

Update the 'callGraph' invocation within your 'getGraphDetails' function as follows:

...
  if (response && response.accessToken) {
    return callGraph<IProfile>(uri, response.accessToken, axiosConfig)
  }
...

Your graph calls are now correctly typed without needing to duplicate your profile definition. Instead, leverage TypeScript's fantastic type inference capabilities to "read your interface" from the function's return type.

Answer №2

Let's tackle your inquiries in reverse order:

Why does TypeScript not flag any issues with this code while the linter does? Shouldn't they both be consistent?

In TypeScript, using type any allows you to assign it to any other type without restrictions. This essentially removes type safety from that specific part of the code. For instance:

const foo: number = 'hello' as any // TypeScript doesn't complain about this

The purpose of the eslint rule is probably to identify instances where assigning a value of type any may not be intentional. It is interesting to note that TypeScript provides the compiler option noImplicitAny which serves a similar purpose.

How can we structure the data in the Promise returned by getGraphProfile to align with the expected format? Creating a TypeScript interface would lead to duplicating code with object defaultState().profile

There are several ways to address this issue. A straightforward solution would involve typing the return value of getGraphDetails:

type GraphDetailsPayload = {
  id: string,
  displayName: string,
  givenName: string,
}

export const getGraphProfile = async (): Promise<GraphDetailsPayload> => {
  ...
}

However, it is generally recommended to define the data at a lower level, such as within the callGraph function:

const callGraph = (
  url: string,
  token: string,
  axiosConfig?: AxiosRequestConfig
): Promise<GraphDetailsPayload> => {
  // implementation details here
}

By structuring it this way, the return value of callGraph is explicitly typed, allowing TypeScript to infer the types for getGraphDetails and getGraphProfile as they ultimately rely on the API response.

Alternatively, considering you use Axios, its TypeScript definition could allow for a more concise approach:

const callGraph = (
  url: string,
  token: string,
  axiosConfig?: AxiosRequestConfig
) => {
  // axios request details here
  return axios<GraphDetailsPayload>({ ...params, ...axiosConfig })
}

In this scenario, the explicit

Promise<GraphDetailsPayload>
return type is omitted, and instead, the GraphDetailsPayload type is directly provided to the axios function using generics. This demonstrates the usage of generics, often encountered when working with TypeScript libraries and functions.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing Gulp locally without the need for global installation or referencing the bin js file

I have a gulpfile.js that runs perfectly when I type 'gulp' into the command line. Essentially, the 'gulp' bash command just calls the specific js file outlined in 'package.json >> bin >> gulp' of the globally ins ...

Tips for activating this effect even when the window is resized during page scrolling

There's a code snippet that enables the header to become unfixed when a specific div reaches the top of the screen and then scrolls with the rest of the content. While this solution works perfectly, I encountered a problem where the calculations for ...

Try rotating a triangle in 3D using either CSS or JavaScript

I want to create a right-angle triangle that looks like this . . . . . . . . . . . . . . . . . . but I also want to add a 3D animation that will transform it into a right angle like this . . . . . ...

Having trouble resolving the dependency injection for stripe-angular

Attempting to integrate the stripe-angular module into my Ionic/AngularJS application. https://github.com/gtramontina/stripe-angular I have installed the module using npm install stripe-angular. This is how I include the dependency in my app: var myApp ...

Managing code requiring document.title in Next.js with Static Site Generation (SSG)

Currently, I have 2 dynamic SSG pages located under /blog/[slug]. Within these pages, I am rendering a component using next/link. When I click on these links to navigate to another slug, I encounter an issue. The problem arises when I want to execute some ...

The logic behind regular expressions

Currently working on building a web application with Angular 6 and I have a query: I'm in the process of developing a custom input component (specifically for text inputs) like so: @Component({ selector: 'input-text', templateUrl: &apos ...

Multiple jQuery load() requests are being triggered by calling it from within the ajaxComplete() callback

I've been stuck on this issue for quite some time now. To troubleshoot, I suggest using Firebug to inspect the AJAX requests. It seems that they are multiplying with each click of the next and previous buttons, causing the page to load slowly: You ca ...

What is the best way to utilize yarn in order to install a GitHub package that utilizes TypeScript and has not yet been compiled?

After making modifications to an npm package and using yarn link <project name> locally, everything works perfectly. However, when pushing it to GitHub and trying to add it to the project with yarn add <repo url>#<branch> instead of yarn ...

Executing the AngularJS nested controller only once

I am still new to Angularjs and just started working on an app that has several "projects" each with a local menu displayed on specific pages. The main navbar with footer is located in the Index.html: <body ng-app="ysi-app" ng-controller="MainControlle ...

Typescript: Defining the correct return type for resolved parameters in promises

Exploring the world of TypeScript, I recently attempted to convert some basic JavaScript promise samples into TypeScript promises. While working on this conversion process, I encountered an issue that has left me puzzled and unable to find a solution even ...

How can I use Angular to dynamically open a video that corresponds to a clicked image?

I need assistance with a functionality where clicking on an image opens a corresponding video on the next page. The issue is that all images have the same ID, making it difficult to link each image to its respective video. Below is the data I am working ...

Prevent rendering a file in node.js using ejs if it cannot be found

I have a specific folder structure under the views directory, containing an EJS file named profile_60113.ejs views docs profile_60113.ejs To dynamically render the file based on the groupID (where data.groupID == 60113), I use the following c ...

Find the sum of every combination of numbers in the array

I've reviewed my code countless times and I'm stumped by the issue. My code is designed to calculate all possible sums of numbers within an array. It works perfectly when there are only 3 numbers in the array, but once I add a fourth number, it i ...

Obtain specific fields from a multidimensional array object using lodash

My dilemma involves working with an object that has the following structure: var data = [ { "inputDate":"2017-11-25T00:00:00.000Z", "billingCycle":6, "total":1 },{ "inputDate":"2017-11-28T00:00:00.000Z", "bi ...

Using jQuery.ajax to manage caching and manipulating timestamps

Currently, I am utilizing the jquery.ajax techniques to transmit GET-requests to the server, with caching disabled by setting cache to false. Everything seems to be working fine so far. It appears that jQuery automatically includes a timestamp (likely epo ...

Issue encountered in Loopback 4: Error with @repository dependency injection (TypeError: Undefined property 'findOne')

As I set up Bearer Token authentication for my Loopback 4 application, I referenced this implementation guide: https://github.com/strongloop/loopback-next/tree/master/packages/authentication. Within my src/providers/auth-strategy.provider.ts file, I encou ...

Exploring the relationship between Typescript, RxJS, Ajax, undefined values

This particular question stands out due to the fact that despite similar issues being previously addressed, none of the existing solutions have proven effective. The problem at hand is as follows: There's a Typescript class that initiates an RxJS.aja ...

Obtaining a specific data point from a JSON object sourced from Twitter

I've tried numerous methods, but I'm struggling to access the data I need in JavaScript. Every time I attempt it, I either get an 'undefined' error or can't access the property of 'x'. Here's the object I'm wor ...

What are the steps to fixing the TS2722 error that says you cannot call a possibly 'undefined' object?

Here is a snippet of code from my project: let bcFunc; if(props.onChange){ bcFunc = someLibrary.addListener(element, 'change',()=>{ props.onChange(element); //This is where I encounter an error }) } The structure of the props ...

Creating a nested list component using an array of objects

Seeking guidance for a coding task I recently completed. I was tasked with creating a multiple nested list from an array of objects. While I achieved the expected result, my code ended up being overly complicated and not very clean. I used a combination of ...