Igor Zvyagin
Igor Zvyagin

Reputation: 474

Using two types for one variable. Typescript

Transferred js typescript, faced with the problem. The following function works with two types of data, I see this error:

Property 'dateTime' does not exist on type 'Operation | OperationCreated'. 
Property 'dateTime' does not exist on type 'OperationCreated'

type DateTime = {
  date: string;
};

type Operation = {
  dateTime: DateTime;
};

type OperationCreated = {
  createdDate: string;
};

const sortByDate = (o1: Operation | OperationCreated, o2: Operation | OperationCreated) =>
  stringToMillisecond(o1.createdDate || o1.dateTime.date) - stringToMillisecond(o2.createdDate || o2.dateTime.date);

Making the first steps in typescript, please help

Upvotes: 2

Views: 3070

Answers (3)

Richard Haddad
Richard Haddad

Reputation: 1014

This error is because the compiler doesn't know if o1 and o2 are Operation or OperationCreated. You may use discriminated unions.

My solution (to clean)

interface DateTime {
  date: string;
}

interface OperationDefault {
  type: 'default';
  dateTime: DateTime;
}

interface OperationCreated {
  type: 'created';
  createdDate: string;
}

type Operation = OperationDefault | OperationCreated;

const sortByDate = (o1: Operation, o2: Operation) => {
  const d1 = o1.type === 'default' ? o1.dateTime.date : o1.createdDate;
  const d2 = o2.type === 'default' ? o2.dateTime.date : o2.createdDate;

  return stringToMillisecond(d1) - stringToMillisecond(d2);
};

Upvotes: 1

jcalz
jcalz

Reputation: 330171

TypeScript does not perceive o1.createdDate || o1.dateTime.date as valid for a few reasons:

  • The biggest one in terms of runtime impact is that you presume that o1.createdDate will only be falsy if it is undefined. But the empty string is also falsy. If someone calls this:

    sortByDate({ createdDate: "" }, { createdDate: "" });
    

    then you will end up calling stringToMillisecond(undefined), which I would suspect is an error. So I would give up on using a falsy check on possibly-string values here.

  • The TypeScript compiler does not let you read a property value of a union type unless every member of the union contains a (possibly optional) property with that key. So o1.createdDate is an error unless you can narrow o1 to a OperationCreated first, by differentiating between the possible values. One way to do this is to use the in operator, like:

    "createdDate" in o1 ? o1.createdDate : o1.dateTime.date; // no error
    

    This works because the compiler uses the in check to narrow o1 to OperationCreated in the "then" clause of the ternary operator, or to OperationDefault in the "else" clause.

So the most straightforward fix I could imagine here is:

const sortByDate = (
  o1: Operation | OperationCreated,
  o2: Operation | OperationCreated
) =>
  stringToMillisecond("createdDate" in o1 ? o1.createdDate : o1.dateTime.date) -
  stringToMillisecond("createdDate" in o2 ? o2.createdDate : o2.dateTime.date);

Okay, hope that helps. Good luck! Link to code

Upvotes: 5

Oyeme
Oyeme

Reputation: 11235

Use an interface instead of a type literal

    interface DateTime {
        date: string;
    }

    interface Operation {
        dateTime: DateTime;
    }

    interface OperationCreated {
        createdDate: string;
    }

    const sortByDate = (
        o1: Operation | OperationCreated,
        o2: Operation | OperationCreated
    ) =>
        stringToMillisecond((o1 as any).createdDate || (o1 as any).dateTime.date) -
        stringToMillisecond((o2 as any).createdDate || (o2 as any).dateTime.date);

Upvotes: -1

Related Questions