Home > Software engineering >  Spread values not being recognized in Create method
Spread values not being recognized in Create method

Time:05-07

Deno has sessionStorage globally available, which is what I'm using as my in-memory database.

I'm working on a method that creates a new museum in sessionStorage. However, the problem is that my create method only works when my Museum type does not extend Record.

Here are my types:

// I want my Museum type to extend the Record type for the extra type safety
interface Museum extends Record<string, unknown> {
  id: string
  name: string
  description: string
  location: {
    lat: number
    lng: number
  }
  createdAt: Date
  updatedAt?: Date
}

// I'm testing two different NewMuseum types
type NewMuseum = Omit<Museum, "id" | "createdAt" | "updatedAt">
// type NewMuseum = Partial<Museum>

Here is my class:

export class Repository {
  
  async getList(): Promise<Museum[]> {
    return JSON.parse(sessionStorage.getItem('museums') || '[]') as Museum[]
  }

  async create(museum: NewMuseum): Promise<Museum> {
    const museumList = await this.getList()
    const newMuseum = {
      id: crypto.randomUUID(),
      ...museum,
      createdAt: new Date(),
    }
    museumList.push(newMuseum)
    sessionStorage.setItem('museums', JSON.stringify(museumList))
    
    return newMuseum
  }
}

When I go to spread the values of the new museum the linter catches an error on newMuseum on the museumList.push(newMuseum) line.

Argument of type '{ createdAt: Date; id: string; }' is not assignable to parameter of type 'Museum'.deno-ts(2345)

newMuseum isn't recognizing the values I'm spreading in it. Instead, newMuseum should look like this:

const newMuseum: {
    createdAt: Date;
    name: string;
    description: string;
    location: {
        lat: number;
        lng: number;
    };
    id: string;
}

How do I get newMuseum to recognize the spread values? Should I be attempting to fix the issue with the Partial utility type instead of Omit?

CodePudding user response:

Casting the newMuseum object as Museum is what did the trick. Here is the updated solution:

export class Repository {
  
  async getList(): Promise<Museum[]> {
    return JSON.parse(sessionStorage.getItem('museums') || '[]') as Museum[]
  }

  async create(museum: NewMuseum): Promise<Museum> {
    const museumList = await this.getList()
    const newMuseum = {
      ...museum,
      id: crypto.randomUUID(),
      createdAt: new Date(),
    } as Museum
    museumList.push(newMuseum)
    sessionStorage.setItem('museums', JSON.stringify(museumList))
    
    return newMuseum
  }
}

Typescript doesn't really know the type of the spread operator so it assumes it's incompatible. as Museum tells the typescript typechecker "no worries, I'm sure this is right."

Shoutout to the Deno Discord for the help!

CodePudding user response:

The values you are expecting to be recognized in your spread operation are not there because the NewMuseum doesn't contain them. Omit is causing the set of keys to be changed:

type NewMuseum = Omit<Museum, "id" | "createdAt" | "updatedAt">;

resolves to

type NewMuseum = {
    [x: string]: unknown;
    [x: number]: unknown;
}

and not to

interface NewMuseum extends Record<string, unknown> {
  name: string;
  description: string;
  location: {
    lat: number;
    lng: number;
  };
}

As such, long before you attempt to spread a NewMuseum the type information for those keys is already lost.

You might consider composing your types rather computing types via utilities like Omit.

e.g.

type MuseumData = Record<string, unknown> & {
  name: string;
  description: string;
  location: {
    lat: number;
    lng: number;
  };
};

type MuseumMetadata = {
  id: string;
  createdAt: Date;
  updatedAt?: Date;
};

type Museum = MuseumData & MuseumMetadata;

type NewMuseum = MuseumData;
  • Related