0
votes

I'm trying to create a template file from an object where the key can be either a string or a function that returns a string:

export const createDynamicTemplate = (
  templateParams: CreateDynamicTemplateParams
) => {
  const { template, projectPath = '', param = '' } = templateParams
  const updatedTemplateArr = Object.keys(template).map((key: string) => {
      return {
        [key]: {
          filePath: `${projectPath}/${key}`,
          template: typeof template[key] === 'function' ? 
          template[key](param) : template[key],
        },
      }
  })
  const updatedTemplate = Object.assign({}, ...updatedTemplateArr)
  return updatedTemplate
}

My interfaces are:

export interface TemplateObject {
  [key: string]: string
}

export interface FunctionalTemplateObject {
  [key: string]: (param: string) => void
}

export interface CreateDynamicTemplateParams {
  template: FunctionalTemplateObject | TemplateObject
  projectPath: string
  param: string
}

It keeps throwing this error in createDynamicTemplate though:

This expression is not callable. Not all constituents of type 'string | ((param: string) => void)' are callable. Type 'string' has no call signatures.

What am I doing wrong here?

1

1 Answers

3
votes

Checking the type of a child property of a variable which references an object will not narrow the type of the variable-object. You can save the value at the key in a separate variable first, then check that value to narrow its type:

const updatedTemplateArr = Object.keys(template).map((key: string) => {
  const item = template[key];
  if (typeof item === 'function') {
    return {
      [key]: {
        filePath: `${projectPath}/${key}`,
        template: item(param),
      },
    }
  }
})

Or, even better, use Object.entries to get the key and value at once. (Also note that there's no need to note the type of the .map parameters - TS can infer it automatically just fine)

const updatedTemplateArr = Object.entries(template).map(([key, value]) => {
  if (typeof value === 'function') {
    return {
      [key]: {
        filePath: `${projectPath}/${key}`,
        template: value(param),
      },
    }
  }
})