4

In the code below typscript compiler shows error in update method, saying that 'any' is not assignable to type 'never'. I noticed that keyof type not working when the type contains boolean mixed with other types. How can I make it compile having mixed type values in the type?

type ConfigState = {
  isAdminSet: boolean;
  isDatabaseConnected: boolean;
  adminName: string;
};

export class ConfigManager {
  state: ConfigState = {
    isAdminSet: false,
    isDatabaseConnected: false,
    adminName: "",
  };

  update(key: keyof ConfigState, value: ConfigState[keyof ConfigState]) {
    this.state[key] = value;
  }
}

But this compiles:

type ConfigState = {
  isAdminSet: boolean;
  isDatabaseConnected: boolean;
};

export class ConfigManager {
  state: ConfigState = {
    isAdminSet: false,
    isDatabaseConnected: false,
  };

  update(key: keyof ConfigState, value: ConfigState[keyof ConfigState]) {
    this.state[key] = value;
  }
}
Romikas
  • 73
  • 1
  • 6

1 Answers1

8

TLDR: TypeScript doesn't know if your value will fit into the chosen property of state.

In your first example, all the properties are boolean, so any is inferred as a boolean. But as soon as you add an other type (here, a string), any can't be inferred without restricting the key. Hence, it is inferred as never, and you can't assign any to never.

In this case, you must (I think) use a generic for this. Moreover, this will ensure type safety.

Take a look at this section of TS documentation: https://www.typescriptlang.org/docs/handbook/2/generics.html#using-type-parameters-in-generic-constraints

type ConfigState = {
  isAdminSet: boolean;
  isDatabaseConnected: boolean;
  adminName: string;
};

export class ConfigManager {
  state: ConfigState = {
    isAdminSet: false,
    isDatabaseConnected: false,
    adminName: "",
  };

  update<Key extends keyof ConfigState>(key: Key, value: ConfigState[Key]) {
    this.state[key] = value;
  }
}
Drarig29
  • 1,902
  • 1
  • 19
  • 42
  • 1
    Thanks, I will likely use it. I edited my question, changed value type from any to ConfigState[keyof ConfigState]. Shouldn't it work? – Romikas Nov 10 '21 at 17:35
  • No this shouldn't work because you are not binding `key` and `value` together. And that's what you need to do. The generic does exactly that. – Drarig29 Nov 10 '21 at 18:39
  • 1
    Roughly speaking, when you have two arguments bound by a generic type, the first argument determines the generic type ; then the second argument has an assigned type in consequence. Here, if you give `'adminName'` as a `key`, the `Key` generic type is determined ; then the value has `ConfigState['adminName']` as type, which is `string`. That's how you should read it. – Drarig29 Nov 10 '21 at 18:45