0

Background:

I'm trying to get typescript to scream when trying to pass an object that has a certain field, but can't make it work for this particular case.

An example code:

import * as React from "react";

// type ReactIntl.MessageValue = string | number | boolean | Date | null | undefined
export type IValues = {
    [key: string]: JSX.Element | ReactIntl.MessageValue;
} | undefined;

export interface INewValues extends Omit<IValues, 'date'> { }

const Foo = () => {
    const values: INewValues = { date: "25/10/2030" }; // No error

    console.log(values);

    return null;
}

Rendering the component will log the following into console:

>> {date: '25/10/2030'}

My question:

Is it possible to tell Typescript that assigning an object with a 'date' field into a variable of type "INewValues" is forbidden?
My goal is to create a type named "INewValues" that has everything "IValues" has, but without a 'date' field.

"IValues" is the type of react-intl's FormattedMessage component's values prop. So basically what I'm trying to do is to forbid developers from passing a values object with a 'date' field.

Note:
When destructuring 'date' from values it does gives the following error as expected:
"Property 'date' does not exist on type 'INewValues'."

const values: INewValues = { date: "25/10/2030" }; // No error

const { date } = values; // Error

console.log(date);

>> 25/10/2030
Elyasaf755
  • 2,239
  • 18
  • 24
  • Your `IValues` can have _any_ string key. You would need to constrain it with something like `key: 'date' | 'time' | 'space' | 'purple'` first... – AKX Mar 31 '22 at 09:53

1 Answers1

2

Disallowing keys doesn't fit the TypeScript model well, but if you really need it, you could declare a type NeverInclude<T, K> like this:

type NeverInclude<T, K extends PropertyKey> = T & Record<K, never>

It will trigger the desired error in Foo:

export interface INewValues extends NeverInclude<IValues, 'date'> {}

const Foo = () => {
    const values: INewValues = {
        elt: <div>'hi'</div>,
        date: "25/10/2030" // ERROR: Type 'string' is not assignable to type 'never'.
    };

    console.log(values);

    return null;
}

It is a little quirky though, and won't fail on destructuring for example, as const { date } = values will just yield a date of type never. But if your main goal is to prevent people from passing objects with a date property, it might suite your use case.

TypeScript playground

Note that the reason Omit<IValues, 'date'> yields an error on destructuring is not because of omitting 'date' (it also happens when any other key is omitted). It's because IValues can be undefined, and applying Omit to a value that can be undefined yields the type {} (because keyof undefined is never). This means all destructuring will fail and you can assign anything except null or undefined to INewValues, e.g.:

const test: INewValues = 42
const { nonDate } = values; // error

If you remove the | undefined from IValues, destructuring no longer errors:

export type IValuesRequired = {
    [key: string]: JSX.Element | ReactIntl.MessageDescriptor
};

export interface INewValuesRequired extends Omit<IValuesRequired, 'date'> {}

const FooRequired = () => {
    const values: INewValuesRequired = { date: "25/10/2030" };
    // different error: Type 'string' is not assignable to type 'Element | MessageDescriptor'.

    const {date} = values // no error
}

(You do get an error on date now, but that's only because the value doesn't match)

TypeScript playground

Oblosys
  • 14,468
  • 3
  • 30
  • 38
  • Thank you so much! Is there a way to achieve both behaviors? (error on destructuring forbidden keys, and error on assigning an object with forbidden keys) – Elyasaf755 Mar 31 '22 at 12:10
  • @Elyasaf755 I don't think so, unfortunately. The destructuring error with the `Omit` version also wasn't caused by omitting `'date'`. I've updated the answer a bit. – Oblosys Mar 31 '22 at 12:53
  • Maybe `Partial>` instead? – jcalz Mar 31 '22 at 14:12
  • 1
    @jcalz You mean in the definition of `NeverInclude`? Won't that just block indexing with non-`date` keys, while still allowing indexing with `date`? https://tsplay.dev/wOJErW – Oblosys Mar 31 '22 at 14:25
  • 1
    I meant `T & Partial...` but yeah if it interacts badly then forget it – jcalz Mar 31 '22 at 14:28