It's possible to assert your way out of the error in the implementation side of the function, but I presume you want to prevent someone from calling things like:
render({ renderItem: (x: number) => x - 2 });
This would cause a runtime error, but the compiler would happily infer number for T and pretend everything is fine.
To fix that, I think maybe we should give up on using default values in the call signature itself and use overloads to represent the two distinct call cases: if you pass an incomplete Props, then it must be a Props<string>. Otherwise, if you pass a complete Props, you can use any generic type you'd like as long as it matches. We'll deal with default values inside the implementation. Which might look like this:
function render(stringProps: Partial<Props<string>>): string[];
function render<T>(otherProps: Props<T>): T[];
function render(props: Partial<Props<any>>) {
const items = props.items || defaultItems;
const renderItem = props.renderItem || defaultRenderItem;
return items.map(renderItem);
}
Using Partial<Props<any>> inside the implementation is similar to using a type assertion, in that the compiler will not complain about props.items || defaultItems not being a T[], since it will definitely be an any[]. Implementations of overloads are not very strongly checked, so we have to be careful.
Anyway, then you'd use it like this, presumably as desired:
// first overload, Partial<Props<string>>
const a = render({}); // string[]
const b = render({ renderItem: x => x.toUpperCase() }); // okay, x must be string
// second overload, Props<T> for generic <T>
const c = render({ items: [1, 2, 3], renderItem: x => x - 2 }); // okay, d is number[]
// errors
const d = render<number>({}); // error! {} is not a Props<number>
const e = render({ renderItem: (x: number) => x - 2 }); // error! if you leave out a property, it must be string-typed
const f = render({ items: [1, 2, 3], renderItem: (x: string) => x.toUpperCase() }); // error! mismatch types
The compiler will now yell at you if you try to call render() with something incomplete unless that incomplete thing is consistent with choosing string as T in the generic version. Hopefully this gives you enough to work with. Good luck!
Playground link to code
render({})? Or do you need to writerender({items: undefined, renderItem: undefined})if you want the defaults?render({})