0

I have this function as below that works well:

function pickAttributes<K extends string, T> (e: Element, attrs: K[]): Record<K, string> {
  return attrs.reduce((obj, key) => {
    return {
      ...(obj as any),
      [key]: e.getAttribute(key)
    }
  }, {})
}

I want to be able to have this function take an optional map function to convert the attributes to a different type.

function pickAttributes<K extends string, T> (e: Element, attrs: K[], mapFn?: (attr: null | string) => T): Record<K, T> {
  if (!mapFn) mapFn = x => String(x)
  return attrs.reduce((obj, key) => {
    return {
      ...(obj as any),
      [key]: mapFn(e.getAttribute(key))
    }
  }, {})
}

However i get this error

TS2322: 
Type '(x: string | null) => string' is not assignable to type '((attr: string | null) => T) | undefined'. 
  Type '(x: string | null) => string' is not assignable to type '(attr: string | null) => T'.
     Type 'string' is not assignable to type 'T'.

I get a similar error if i try to use a default parameter.

The changed function seems to work as expected without trying to add a default

function pickAttributes<K extends string, T> (e: Element, attrs: K[], mapFn?: (attr: null | string) => T): Record<K, T> {
  return attrs.reduce((obj, key) => {
    return {
      ...(obj as any),
      [key]: e.getAttribute(key)
    }
  }, {})
}

1 Answer 1

1

First: I'm not sure what could extend string, so I would drop the generic parameter K.

But anyway: you want your return type to vary, based on the presence of the mapFn argument. I would solve that by defining two overloads of the function. And in the implementation, by using a union type string | T:

function pickAttributes<K extends string>(e: Element, attrs: K[]): Record<K, string>;
function pickAttributes<K extends string, T> (e: Element, attrs: K[], mapFn: (attr: null | string) => T): Record<K, T>;
function pickAttributes<K extends string, T> (e: Element, attrs: K[], mapFn?: (attr: null | string) => T): Record<K, string | T> {
  const fn = mapFn || (x => String(x));
  return attrs.reduce((obj, key) => {
    return {
      ...(obj as any),
      [key]: fn(e.getAttribute(key))
    }
  }, {});
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks the overload + union type seemed to do the trick. The reason why i use K extends string is because im using K as an enum. This way typescript knows that the return object has the strings in attrs as properties via Record

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.