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