Wrapping an anonymous function in a wrapper function in Typescript can prevent the inferred typing

I am encountering an issue with typing while coding:

function identity<T>(v: T): T{ return v; }

function execute(fn: {(n: number):string}) {}

execute((n) => {
    // type of n is 'number'
    return n.toFixed();
})

execute(identity((n) => {
    // type of n is 'any'
    return n.toFixed();
}))

When a typed higher-order function execute receives a function, the arguments of that anonymous function are typed via inference. However, passing that anonymous function to a wrapper identity function causes those inferred types to be lost. Is there any adjustments I could make to the construction of execute or identity that can allow those typings to still be inferred?

NOTE For simplicity, identity is a pure function here. In actual practice it is not, but should have the same typing as this identity function. See checkpoint in context of question for more detail.

see it in the TS Playground


Context

This is the generic form of a problem I was facing when handling data within a React component lifecycle. To avoid calling setState on an unmounted component, I implemented logic to prevent the load callback from executing.

function loadData():Promise<MyDataType> {/*...*/}    

// Wraps the passed function (handleDataLoaded), 
// such that when the returned function is executed the 
// passed function is conditionally executed depending 
// on closure state.
function checkpoint(fn){/*...*/}

// Add data to the state of the component
function handleDataLoaded(val: MyDataType){/*...*/}



// react lifecycle hook componentDidMount
    loadData()
        .then(checkpoint(handleDataLoaded));

// react lifecycle hook componentWillUnmount 
// adjusts state of checkpoint's closure such that handleDataloaded
// is not fired after componentWillUnmount

Answer №1

It seems that the code you provided is essentially equivalent to:

function identity<T>(v: T){ return v; }

function execute(fn: {(n: number):string}) {}

execute((n) => {
    // type of n is 'number'
    return n.toFixed();
})

var func = identity((n) => {
    // type of n is 'any'
    return n.toFixed();
});
execute(func);

However, when you explicitly specify the generic parameter as follows:

var func = identity<number>((n) => {
    // type of n is 'any'
    return n.toFixed();
});

You will encounter a compiler error:

https://i.sstatic.net/OvV4o.png

As you can see, you are passing a function instead of a number.

If you elaborate on your intentions, we may be able to offer a solution.

Answer №2

There is no annoyance at all. It seems more like a case of encountering a flaw in your reasoning (in your thoughts). The absence of strict mode presents another complication.

/* execute( */  identity((n) => {
    // the type of n is 'any', as it would naturally be
    // There are no type constraints in the `identity` function, 
    // so there is no logical basis to anticipate `n` to have the type `number`
    // I've omitted the wrapping by the `execute` function 
    // to avoid any confusion on your part. Regardless,
    // whether it's included or not, you should first determine
    // the structure and nature of the underlying expression,
    // as this is how Typescript deduces them.
    return n.toFixed();
}) /* ) */

However

function identity<T extends {(n: number): string}>(v: T): T{ return v; }

/* execute( */ identity((n) => {
    // n is of type 'number' due to the constraint on the type parameter `T` in the `identity` function
    return n.toFixed();
}) /* ) */ 

You can also do this:

/* execute( */ identity<{(n: number): string}>((n) => {
    // n is of type 'number'
    return n.toFixed();
}) /* ) */

And

execute(identity((n: string) => {
    // this triggers a TS error
    // "Argument of type '(n: string) => () => string' is not 
    // assignable to parameter of type '(n: number) => string'"
    return n.toFixed;
}))

In conclusion, it is crucial to always utilize strict mode (include "strict": true in the "compilerOptions" section of tsconfig.json) to prevent encountering such issues.

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

Using TypeScript to asynchronously combine multiple Promises with the await keyword

In my code, I have a variable that combines multiple promises using async/await and concatenates them into a single object const traversals = (await traverseSchemas({filename:"my-validation-schema.json"}).concat([ _.zipObject( [&quo ...

Can you explain the meaning of <T = {}>?

While browsing through the documentation, I came across this generic type: type GConstructor<T = {}> = new (...args: any[]) => T; https://www.typescriptlang.org/docs/handbook/mixins.html Above this line, there is a brief mention that it is a Gene ...

What makes fastify-plugin better than simply calling a regular function?

I recently came across a detailed explanation of how fastify-plugin operates and its functionality. Despite understanding the concept, I am left with a lingering question; what sets it apart from a standard function call without using the .register() metho ...

Guide to configuring a function to display the maximum value on a boxplot in Highcharts

I'm currently using Angular in combination with the highcharts boxplot API. While I am aware that I can manually set the max value of the y-axis in the chart configuration, such as: max: 100, tickInterval: 10. There's now a need for me to dynami ...

What is the method for creating a new array of objects in Typescript with no initial elements?

After retrieving a collection of data documents, I am iterating through them to form an object named 'Item'; each Item comprises keys for 'amount' and 'id'. My goal is to add each created Item object to an array called ' ...

Ionic: Fixed button located at the bottom of a specific ion-slide

I've been creating a series of slides with questions, and the final slide serves as a summary of the previously answered questions. I want to ensure that the submit button is always visible at the bottom of this last slide. However, I've encounte ...

Rendering a component in React based on multiple conditions

Checking sessionStorage and another state variable before rendering a component is essential for my application. I want to avoid showing the same message multiple times within the same session. This is how I have implemented it: const addSession = (noteId: ...

Issue encountered when attempting to develop a countdown timer using Typescript

I am currently working on a countdown timer using Typescript that includes setting an alarm. I have managed to receive input from the time attribute, converted it using .getTime(), subtracted the current .getTime(), and displayed the result in the consol ...

How can I extract a specific data value from a JSON file in Ionic 2?

Here is the JSON data: [ { "id": 1, "label": "saw", "total": "100" }, { "id": 2, "label": "saw1", "total": "300" }, { "id": 3, "label": "saw2", "total": "400" } ] Below is my Typescript code snippet: this. ...

Unable to retrieve a boolean with the statement "return of(false)"

My objective is to retrieve data, store it, and return either true or false based on the operation outcome. Initially, I attempted to make the call and then use return of(true) to create an observable. The method I have is as follows. setValidations(): Ob ...

Struggling with TypeScript compilation in a Vue.js project? Encounter error code TS2352

Here is my code snippet from window.ts import Vue from 'vue' interface BrowserWindow extends Window { app: Vue } const browserWindow = window as BrowserWindow export default browserWindow Encountering a compilation error Error message: TS2 ...

Eslint is back and it's cracking down on unused variables with no

I've configured eslint to alert me about unused variables rules: { '@typescript-eslint/no-unused-vars': ['error', { args: 'none' }], } Presently, I have a TypeScript class structured like this: import { User } from &ap ...

Creating a method that generates an object containing both a getter and setter functionality, which is determined by a string parameter

I'm struggling to come up with the correct typing for a function that creates an object with setter and getter properties. I believe using template string literals might be the way to go, but I'm having trouble figuring out the right combination ...

Oops! The last loader did not provide a Buffer or String as expected

After converting my GraphQL query and HOC component to typescript, I encountered the following error: ERROR in ./client/components/Protected.Route.tsx Module build failed: Error: Final loader (./node_modules/awesome-typescript-loader/dist/entry.js) didn ...

Error caused by properties of a variable derived from an interface in the compiler

I'm confused as to why the compiler is saying that the properties "name" and "surname" don't exist on type "ITest1" within function test1. It's a bit unclear to me: interface ITest1{ name: string; surname: string; age: number; } ...

Exploring the functionality of generic components in React Native when using TypeScript

As an illustration, consider export class FlatList<ItemT> extends React.Component<FlatListProps<ItemT>> which incorporates the generic type ItemT. How can I utilize it in a .tsx code? When not parametrized, it appears like this: <Flat ...

Tips for specifying a variable as a mandatory key attribute within an array

Is there a way to dynamically determine the type of key attribute in an array? const arr = [ { key: 'a' }, { key: 'b' }, { key: 'c' }, ]; type key = ??? // Possible values for key are 'a', 'b', or &a ...

React 18 Fragment expressing concern about an excessive amount of offspring

Recently, I attempted to integrate Storybook into my React application, and it caused a major disruption. Despite restoring from a backup, the absence of the node_modules folder led to issues when trying 'npm install' to recover. Currently, Types ...

Encountering the error "TypeError: null is not an object (evaluating '_ref.user')" with onAuthStateChanged in React Native using Firebase and useContext

I'm encountering an error in my useCachedResources.ts file and I'm uncertain of the cause. These three files are what I'm currently working with. I have a suspicion that the issue lies in the initial null value, but I am conditionally render ...

What is the best way to integrate ContextualMenu with Persona in Office Fabric UI React?

Currently, I am utilizing Office Fabric UI React to work on a project. My goal is to implement a ContextualMenu in conjunction with the Persona object. In this particular example, I am demonstrating how ContextualMenu can be applied directly to differ ...