48560

Back propagate type inference when currying in typescript

Question:

I hope the title makes sense. What I want to do is to have a factory function that takes a function taking an argument which will be supplied in later call of the returned function. In essence:

const f = <B extends keyof any>(arg: B, fn: (props: A) => void) => <A extends Record<B, any>>(obj: A): Omit<A, B> => { fn(obj) delete obj[arg] return obj }

Obviously A is not available to the first function definition and it has to be in the second function definition to be inferred properly (see my previous question <a href="https://stackoverflow.com/questions/53466131/how-to-write-omit-function-with-proper-types-in-typescript" rel="nofollow">How to write omit function with proper types in typescript</a>).

I assume there could be at least a way to constrain A to both A extends Record<B, any> as that is needed so that the first arg is actually a key from the object supplied later, while at the same time it has to be the same as the fn props.

The example is contrived but in essence something similar should be needed for redux connect style HOC. Issue is I do not understand redux type definitions enough to know how to take them and modify for my usecase.

EDIT: Example of the HOC I want to create:

export const withAction = <A extends keyof any, B extends object>( actionName: A, // The B here should actually be OuterProps actionFunc: (props: B, ...args: any[]) => Promise<any>, ) => <InnerProps extends object, OuterProps extends Omit<InnerProps, A>>( WrappedComponent: React.ComponentType<InnerProps>, ): React.ComponentType<OuterProps> => { return (props: OuterProps) => { // This is a react hook, but basically it just wraps the function with some // state so the action here is an object with loading, error, response and run // attributes. We just need to wrap it like this to be able to reuse hook // as a HOC for class based components. const action = useAction((...args) => { // At this moment the props here does not match the function arguments and // it triggers a TS error return actionFunc(props, ...args) }) // The action is injected here as the actionName return <WrappedComponent {...props} {...{ [actionName]: action }} /> } } // Usage: class Component extends React.Component<{ id: number, loadData: any }> {} // Here I would like to check that 'loadData' is actually something that the // component want to consume and that 'id' is a also a part of the props while // 'somethingElse' should trigger an error which it does not at the moment. const ComponentWithAction = withAction('loadData', ({ id, somethingElse }) => API.loadData(id), )(Component) // Here the ComponentWithAction should be React.ComponentType<{id: number}> render(<ComponentWithAction id={1} />)

Answer1:

So far I managed to partially do what I want and I think it is probably only thing that is possible here as mentioned by @jcalz in a comment.

In the general example:

// Adding '& C' to second generic params const f = <B extends keyof any, C extends object>(arg: B, fn: (props: C) => void) => <A extends Record<B, any> & C>(obj: A): Omit<A, B> => { fn(obj) delete obj[arg] return obj } const a = f('test', (props: { another: number }) => {}) // TS error, another: number is missing which is good const b = a({ test: 1 }) const d = a({ test: 1, another: 1 }) // test does not exist which is good const c = d.test

And in the HOC example:

export const withAction = <A extends keyof any, B extends object>( actionName: A, actionFunc: (props: B, ...args: any[]) => Promise<any>, ) => < InnerProps extends object, // The only change is here that OuterProps has '& B' OuterProps extends Omit<InnerProps, A> & B >( WrappedComponent: React.ComponentType<InnerProps>, ): React.ComponentType<OuterProps> => { return (props: OuterProps) => { const action = useAction((...args) => { return actionFunc(props, ...args) }) return <WrappedComponent {...props} {...{ [actionName]: action }} /> } } // Usage: class Component extends React.Component<{ id: number; loadData: any }> {} const ComponentWithAction = withAction( 'loadData', // somethingElse here does not trigger the error which would be nice but // probably not possible (props: { id: number; somethingElse: number }) => API.loadData(props.id), )(Component) // Here the ComponentWithAction is React.ComponentType<{id: number, somethingElse: number }> // and so TS correctly errors that somethingElse is missing render(<ComponentWithAction id={1} />) // This is correct but strangely some Webstorm inspection thinks loadData is // required and missing here. So far first time I see Webstorm trip with TS. render(<ComponentWithAction id={1} somethingElse={3} />)

So this seem to be type safe enough and correct for my usecase, my only nitpick is that I would like to have the type error in the actionFunction instead of having it in the component usage later on. Probably not possible but I will leave this open for some time to see if there is nobody who would know a way.

Recommend

  • React Functional Components with hooks vs Class Components
  • Can a team admin create a distribution provisioning profile? Or just the team agent?
  • Redux - where to prepare data
  • How constrain the list of users who can receive the e-mail in jenkins on build failure?
  • “babelHelpers.asyncToGenerator is not a function” on React-Native 0.16.0 and 0.17.0
  • SugarORM query from multiple tables?
  • NSTextField margin and padding? (Swift)
  • how would I constrain to non-function?
  • How To Pass Props From Redux Store into Apollo GraphQL Query
  • jQueryUI Draggable: Constrain draggability to a single axis?
  • Greek letters in a GUI - PYTHON
  • What's the point of nonfinal singleton objects in scala?
  • React Native + Redux: What's the best and preferred navigation?
  • Class implementation in a header file == bad style? [duplicate]
  • Multiple versions of iTunesArtwork in one project?
  • 'include' of functions in groovy scripts
  • Dynamically switching connect in Modelica
  • nonblocking BIO_do_connect blocked when there is no internet connected
  • Redux Form - Not able to type anything in input
  • how to avoid repetitive constructor in children
  • Get history of file changes from TFS to implement custom “blame”-behaviour of exceptions
  • Installing Apache MyFaces 2 on WildFly 8.2.0
  • How do I alternate colors in Flat List (React Native)
  • Listbox within Listbox and scrolling trouble in Windows Phone 7 Silverlight
  • Spark fat jar to run multiple versions on YARN
  • Using jQuery closest() method with class selector
  • Display issues when we change from one jquery mobile page to another in firefox
  • Different response to non-authenticated users and AJAX calls
  • Array.prototype.includes - not transformed with babel
  • Arrow is showed instead of the material design version hamburger icon. Why doesn't syncState in
  • ActionScript 2 vs ActionScript 3 performance
  • jquery mobile loadPage not working
  • Unanticipated behavior
  • Data Validation Drop Down Box Arrow Disappearing
  • json Serialization in asp
  • How can I get HTML syntax highlighting in my editor for CakePHP?
  • Why can't I rebase on to an ancestor of source changesets if on a different branch?
  • How do I configure my settings file to work with unit tests?
  • IndexOutOfRangeException on multidimensional array despite using GetLength check
  • Binding checkboxes to object values in AngularJs