3

I have an app where I use a json structure to define the columns of a table.

Each column has a key and label property and an optional transformFunc property that takes a function that transforms the data to be displayed in the column.

This is the type for Column:

type Column = {
  key: string
  label: string
  transformFunc?: (value: string | number | Date) => string
};

transformFunc either takes a string, number or Date as an argument. However, I'm not able to properly type it this way:

const col: Column = {
  key: 'date',
  label: 'Date',
  transformFunc: (value: string) => value.substring(0, 5)  // TS2322: Type '(value: string) => string' is not assignable to type '(value: string | number | Date) => string'
}

How can I solve this?

I looked into generics but I don't see how to do this.

1
  • One way is to typecast it like - transformFunc: (value) => (value as string).substring(0, 5) Commented Jun 24, 2022 at 11:36

4 Answers 4

4

In generic way

type Column<T extends string | number | Date> = {
  key: string
  label: string
  transformFunc?: (value: T) => string
};

const col: Column<string> = {
  key: 'date',
  label: 'Date',
  transformFunc: (value) => value.substring(0, 5)
}
Sign up to request clarification or add additional context in comments.

Comments

0

Because of your type definition, a transformFunc must accept all of the union's type, so you can't create a Column with a transformFunc accepting just one.

If you know what type of data a specific column will hold, you can use a type assertion in your function:

const col: Column = {
  key: 'date',
  label: 'Date',
  transformFunc: (value: string | number | Date) =>
    (value as string).substring(0, 5)
}

Comments

0

After defining Column, you could had no need for any type when using it. You just use values, like 'date' and 'Date', give to transformFunc a function without any type, then use type narrowing to have what you want. Something like the below code.

See this TypeScript playgroud I created.

type Column = {
  key: string
  label: string
  transformFunc?: (value: string | number | Date) => string
};

const col: Column = {
  key: 'date',
  label: 'Date',
  transformFunc: (value) => {if (typeof value == "string") {return value.substring(0, 5)}else return ""} 
}

Comments

0

Alternatively if you don't want to use a generic, the union should instead be outside of the function scope

type Column = {
  key: string
  label: string
  transformFunc?: 
    ((value: string) => string) | 
    ((value: number) => string) | 
    ((value: Date) => string)
};

const col: Column = {
  key: 'date',
  label: 'Date',
  transformFunc: (value: string) => value.substring(0, 5)
}

Or if you want to infer the type based on the value of transformFunc you can use a helper function

type ColumnGeneric<T extends string | number | Date> = {
  key: string
  label: string
  transformFunc?: (value: string | number | Date) => string
}

const columnInferenceBuilder = <
  _ extends ColumnGeneric<FunctionProp>, 
  FunctionProp extends string | number | Date
>(o: {
  key: string;
  label: string;
  transformFunc: (value: FunctionProp) => string
}) => o

const inferredColumn = columnInferenceBuilder({
  key: 'date',
  label: 'Date',
  transformFunc: (value: string) => 'foobar',
})

This pattern might be more useful if you had an extra property of value that corresponds to the transformFunc arguments (or somehow related otherwise to discriminate with)

// imagine its imported
const importedData = {
  key: 'foo',
  label: 'Foo',
  value: 0
} as const

const inferredColumn2 = columnInferenceBuilder2({
  ...importedData,
  transformFunc: (value) => 'foobar',
  //   ^? (value: 0) => string
})

View these examples on TS Playground

Comments

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.