0

Being new to Typescript I cannot understand how to attach a method to a function. The code works but the types are not exported correctly for autocompletion. Can please somebody help and tell me what I am doing wrong?

import * as CSS from 'csstype';

export type AsType = 'div' | 'span' | 'main';
export interface InstanceType {
  /**
  * Set HTML tag
  * @param as Tag or component
  */
  as: (tagName: AsType) => any;
}

// base has methods render(props: CSS.Properties) and as(a: AsType)
const boxInstance = new Base();

function attachMethods(Component, instance) {
  Component.as = function as(asProp: AsType) {
    return instance.as(asProp);
  }
}

function Box(props: CSS.Properties): InstanceType {
  return boxInstance.render(props);
}

attachMethods(Box, boxInstance);

In another module Box is imported like this, but autocompletion does not work. I use Microbundle so the *.d.ts should be created correctly. Box renders a react component.

import { Box } from 'package';

// autocompletion or JSDoc does not work here
const Boxi = Box.as('div');
// returns <div>Box</div>
<Boxi>Box</Boxi>

Also tried Object.assign liked described here without any change.

const Box: InstanceType = Object.assign(
 (props: CSS.properties) => boxInstance.render(props),
 {
   as: function as(asProp: AsType) {
    return instance.as(asProp);
   }
 }
)

Typescript Playground

Edited 28.08
According to the answer from Aluan Haddad the parameter name for JSDoc was wrong. It should be. But JSDoc was not working because the InstanceType was not correct. Please see answer from ccarton.

* @param tagTame - Tag or component

Edited 29.08 A workaround tried. This removes typescript errors and TSDoc works.

interface ComponentType extends InstanceType {
 (props: CSS.Properties): any // or ReturnType<typeof render> function
}

const Box: ComponentType = function Box(props: CSS.Properties) {
  return box.render(props);
} as ComponentType;

Playgrounds
Settings all types to any I still end up with either cannot invoke as function or as is missing in type.

Espen Finnesand
  • 475
  • 7
  • 22
  • The parameter names are different. – Aluan Haddad Aug 26 '20 at 12:19
  • Thank for the feedback. Did you mean *AsType*? It was wrongly written as *As*. It is a simplified example. – Espen Finnesand Aug 26 '20 at 12:31
  • I mean that the JSDoc comment refers to a parameter name that is unspecified – Aluan Haddad Aug 26 '20 at 12:36
  • thanks again. That was also a typo. Added part of box.d.ts to the question. **as** seems to be missing from **namespace**. – Espen Finnesand Aug 26 '20 at 12:55
  • It should be `@param tagName` since you are documenting a parameter named `tagName` – Aluan Haddad Aug 26 '20 at 18:35
  • thank you! After applying the solution from @ccarton TSDoc is also working. – Espen Finnesand Aug 27 '20 at 20:34
  • Ok, looking at the playground link there are a lot of problems with that code. You're using `Base` as if it implements `InstanceType` but it doesn't. The signatures for the `as` functions are not the same. Also, `Box` says it returns `ComponentType` but it actually returns `Base.render()` which is not `ComponentType` but another React type. You should read the error messages carefully, they actually provide a lot of information about the problems if you make the effort to decipher them. – ccarton Aug 28 '20 at 17:31
  • @ccarton Yes I try to, but being a newbie it is sadly not so easy. If I put all types to any I still end up in a rabbit hole. I provided to playgrounds to my answer. It would be very kind of you if you can provide a playground that shows how to dynamically attach a method to a callable function. – Espen Finnesand Aug 29 '20 at 09:50

1 Answers1

0

If you change attachMethods to return the modified object you can achieve what you want with a bit of type casting. We also should use Object.defineProperty as the safest way to modify an existing object:

function attachMethods<T> (component: T, instance: InstanceType): T & InstanceType {
  Object.defineProperty(component, 'as', {
      value: (asProp: AsType) => instance.as(asProp)
  })
  return component as any
}
   
function BoxFunction (props: CSS.Properties): InstanceType {
  return boxInstance.render(props);
}

const Box = attachMethods(BoxFunction, boxInstance);
ccarton
  • 3,556
  • 16
  • 17
  • Thank you for taking the time to answer this. Autocompletion/JSDoc still does not work. Do I need to do something special for autocompletion to work or is it enough having *.d.ts files? Get a TS error **Property 'as' does not exist on type 'T'**. namespace declaration has not changed, doest it need to? – Espen Finnesand Aug 26 '20 at 14:04
  • @EspenFinnesand Note that I'm using `component['as']` instead of `component.as`. That was to avoid that error. – ccarton Aug 26 '20 at 14:21
  • I tried that also but got **Element implicitly has an 'any' type because expression of type '"as"' can't be used to index type 'unknown'.** – Espen Finnesand Aug 26 '20 at 14:53
  • @EspenFinnesand If you can provide an [mcve](https://stackoverflow.com/help/minimal-reproducible-example) (a reproducible example), either in the [typescript playground](https://www.typescriptlang.org/play) or elsewhere then I will have a closer look. – ccarton Aug 27 '20 at 09:54
  • Thank you. It is an OSS project. Updated it to your answer with a twist: **attachMethods(BoxFunction as InstanceType, boxInstance)**. That seem to work. Is that a good solution. [attachMethods](https://github.com/secretlifeof/juhuui/blob/master/src/base/attachMethodsToInstance.ts), [Box](https://github.com/secretlifeof/juhuui/blob/master/src/box.ts) and [types](https://github.com/secretlifeof/juhuui/blob/master/src/types.ts). If it is more practical for you I can make a mcve? – Espen Finnesand Aug 27 '20 at 20:33
  • @EspenFinnesand That might not work because then it might not let you just call `Box(props)` because it might not know that `Box` is a function. I can't tell from what you've provided why it would think the type was unknown, it is defined and known right there before the call, so there are some other factors in play. – ccarton Aug 28 '20 at 12:30
  • You are right. Calling Box directly did not work. Added a solution to my answer that remove typescript errors. I will try to put together an MCVE over the next days. – Espen Finnesand Aug 28 '20 at 12:47
  • @EspenFinnesand I've realized that `Object.defineProperty` is the better way to implement `attachMethods` and might solve this issue. I've updated the answer, give this new version a try. – ccarton Aug 28 '20 at 13:02
  • I tried that now but it did not change. Added a typescript playground to my question. Hope that make things easier. – Espen Finnesand Aug 28 '20 at 16:40