1

The problem is TypeScript thinks the value I provide for the UiConfigContext.Provider may be undefined. Probably because of the ? config optional param however, I do provide the default { [Permission.Read]: true}. I don't understand why it still thinks it could ever be undefined. I want this function's parameter to be optional so anything like that would work:

renderTestComponent();
renderTestComponent({ config: { [Permission.Read]: true });
renderTestComponent({ enterDetails: true });
renderTestComponent({ config: { [Permission.Read]: true }, enterDetails: true );

Here's my code (replicated error in TypeScript playground)

import React, { createContext } from 'react';

enum Permission {
    Read = 'Read',
    Write = 'Write',
}

type Configuration = Partial<Record<Permission, boolean>>;

export const UiConfigContext = createContext<Configuration | null>(null);

function renderTestComponent(args: {
    config?: Configuration;
    enterDetails?: boolean;
} = {
    config: { [Permission.Read]: true},
    enterDetails: false
}) {
    <UiConfigContext.Provider value={args.config}>
        Test
    </UiConfigContext.Provider>
}
LazioTibijczyk
  • 1,701
  • 21
  • 48
  • 1
    Because it *can* be undefined. If you do `console.log(args.config)` and test it with your example calls it will be undefined for the third call. This is, because with `{ enterDetails: true }` it won't use the default for the whole object (you already passed it an object). Take a look at this question for more info: https://stackoverflow.com/questions/26578167/es6-object-destructuring-default-parameters/26578323 – A_A Oct 26 '21 at 13:53
  • @A_A I believe this is not the case: `(function test({a = "foo", b = "bar"} = {b:"test"}) { console.log(a + " " + b); })();`Is logging `foo test`. So the default is taken for `a`. – henk Oct 26 '21 at 14:05

2 Answers2

3

By adding a question mark to the config?: type definition, TypeScript assumes that the property might end up undefined. If you know for sure, that it will never be undefined, remove the optional flag. Otherwise check if it is defined

...{args.config ? <UiConfigContext.Provider value={args.config}>
        Test
</UiConfigContext.Provider>: null}...
henk
  • 2,677
  • 2
  • 18
  • 49
  • 1
    Even default parameter won't solve this? I will always provide some kind of config whether it's by parameter or default parameter. – LazioTibijczyk Oct 26 '21 at 13:56
  • 2
    @LazioTibijczyk default value for a parameter will only be used if the passed argument is `undefined`. With your current types `renderTestComponent` will accept an empty object as an argument and in this case `args.config` will be `undefined` which is not valid for `value`. – marzelin Oct 26 '21 at 14:14
  • Default parameter will not solve this, because if you pass an empty object or an object with only one property, the other property will end up undefined. But default values for the individual object properties will solve this as well. – henk Oct 26 '21 at 14:28
3

There are 2 problems: Firstly, the type with ? tells Typescript that it could be undefined. Secondly, with your current default parameters it actually could be undefined at runtime, depending on how you call it.

The first problem can be solved, by removing the ?.

function renderTestComponent(args: {
    config: Configuration; // <-- removed the ?
    enterDetails: boolean; // <-- removed the ?
} = {
    config: { [Permission.Read]: true},
    enterDetails: false
}) {
    // ...
}

However, now it doesn't accept to call it with a partial object, e.g. renderTestComponent({}); or renderTestComponent({ enterDetails: true });. In such a case it doesn't assign the default parameter, because you provided it a value ({}). Because config is undefined, typescript complains now.

For this second problem I'd suggest to use default parameters and destructuring as follows. Here we assign the default value in each property, instead of the whole object at once:

function renderTestComponent({
    config = { [Permission.Read]: true },
    enterDetails = false
  } = {}) {
    console.log(config)
} 

renderTestComponent();
renderTestComponent({ config: { [Permission.Read]: true }});
renderTestComponent({ enterDetails: true });
renderTestComponent({ config: { [Permission.Read]: true }, enterDetails: true });

Here is a working example: https://tsplay.dev/m0L6Gm

A_A
  • 1,832
  • 2
  • 11
  • 17