0

Is there any way in typescript to have a property name generated dynamically by modifying another property name?

I'm trying to write some typings for a React higher order component that modifies the props passed to a wrapped component. If you configure the hoc with a property name (e.g. value) it will also generate a property name from that (in this case, defaultValue).

A sample of partially working code on the Typescript playground.

Ideally, there would be no error when attempting to access defaultValue.

Pre101
  • 2,370
  • 16
  • 23
  • 1
    Your use-case sound similar to [this one](https://stackoverflow.com/questions/44323441/changing-property-name-in-typescript-mapped-type), but there are limitations. You could potentially consider putting all of the defaults in a `defaults` property instead, then you wouldn't need to change the name of the properties. Nesting the defaults under another property isn't exactly what you wanted, but it may suffice. – Jake Holzinger Sep 24 '19 at 23:12
  • Thank you for the pointer! I couldn't find that sample during my searches. We may indeed need to figure a different approach for the generic case or just make the other member names configurable so the user can specify them as well. – Pre101 Sep 25 '19 at 17:02

1 Answers1

1

This was not possible back when you asked this question, but it is possible now with the introduction of Template Literal Types in typescript version 4.1.

We define a type that maps from a string literal "name" to its corresponding "defaultName".

type DefaultName<PropName extends string> = `default${Capitalize<PropName>}`

We declare the Props should have values for both the property and its default.

type Props<PropName extends string, Value = boolean> = {
    [K in PropName]: Value;
} & {
    [K in DefaultName<PropName>]: Value;
}

I moved the manipulation of the string into a separate function which asserts the correct return type for the string literal which it creates. (Though technically, the hoc works without this assertion due to the as Props<PropName>).

function computeName<T extends string>(name: T): DefaultName<T> {
    const computedName = 'default' + name.slice(0, 1).toUpperCase() + name.slice(1);
    // need to assert the return type here, otherwise it's just `string`
    return computedName as DefaultName<T>;
}

Your hoc is basically the same. We still need to assert as Props<PropName> when calling fn, otherwise the object gets interpreted as { [x: string]: boolean; }.

function hoc<PropName extends string>(config: Config<PropName>, fn: Callback<PropName>) {
    fn({
        [config.name]: true,
        [computeName(config.name)]: false
    } as Props<PropName>);
}

The magic happens in test, where now obj is known to have a property obj.defaultValue. This fixes the error that you had before.

const test = hoc({ name: 'value' }, (obj) => {
    console.log(obj.value, obj.defaultValue); // no more errors
});

Typescript Playground Link

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102