Proxied Object Assignment Type Error TypeScript

Viewed 50

I have a cache that my app uses and the cache is wrapped in a Proxy that manages the expiration validation.

Here are some of the List definitions from my Lists.d.ts file for reference but I don't think the issue exists here.

interface MetaData {
  Title: string | null;
  FileSystemObjectType: number;
  Id: number;
  // ....
}

// The CreationProps types just define the fields in the object
type DeviceCreationProps = {
  Model: string;
  Manufacturer: string;
  // ...
};

type CommentData = MetaData & CommentCreationProps;
type DeviceData = MetaData & DeviceCreationProps;
type UserData = MetaData & UserCreationProps;

type ListDataMap = {
  Comments: CommentData;
  Devices: DeviceData;
  Users: UserData;
};

type ListName = keyof ListDataMap;

Now in my ListItemCache.ts file I have the following code:

type CacheExpirationMapValue = {
  // [list name] : expiration date object
  [List in ListName]?: Date;
};
const CacheExpirationMap: CacheExpirationMapValue = {};

type RawListItemCacheValue = {
  // [name of list]: array of list items
  [List in ListName]?: ListDataMap[List][];
};
const RawListItemCache: RawListItemCacheValue = {};

const maxAge = 1000 * 60 * 5;

// This is a proxied object that allows us to handle the expirations when fetching cached objects
const ListItemCache = new Proxy(RawListItemCache, {
  set<List extends ListName>(obj, prop: List, value: ListDataMap[List][]) {
    const expires = new Date(Date.now() + maxAge);

    CacheExpirationMap[prop] = expires;

    obj[prop] = value;

    return true;
  },

  get(obj, prop) {
    const item = obj[prop];
    const now = new Date();

    if (item && !!CacheExpirationMap[prop] && CacheExpirationMap[prop] > now) {
      return item;
    }

    return [];
  },
});

export default ListItemCache;

Everything is fine and I don't get any issues in this file, but when I try to set the value of the cache in another file for instance:

export default function usePopulateCache<List extends ListName>(list: List) {
  const { sp } = useContext(SPContext);
  useEffect(() => {
    sp.web.lists
      .getByTitle(list)
      .items<ListDataMap[List][]>()
      .then((items) => {
        ListItemCache[list] = items;
      });
  }, []);
}

I get the following error in the assignment: enter image description here

I'm not sure why this is because the List type that indexes both of them is the same and RawListItemCacheValue[List] should be the same as ListDataMap[List][] according to its type definition.

1 Answers

the problem here is that RawListItemCacheValue[List] is not the same as ListDataMap[List][], even though it really looks like it is.

If you check the error you will see whats the difference is in that line: CommentData | DeviceData | UserData is not assignable to type CommentData.

What this means is that when yo do this:

type RawListItemCacheValue = {
  // [name of list]: array of list items
  [List in ListName]?: ListDataMap[List][];
};

somehow you are telling tsc this:

type RawListItemCacheValue = {
  [CommentData]?: ListDataMap[CommentData][];
  [DeviceData]?: ListDataMap[DeviceData][];
  {...}
};

So the key and the value are correlated.

The problem is that when you do ListDataMap[List][] with List being === to keyof ListName, this value can be anyone on ListDataMap. So when you do ListItemCache[list] = items; you are really doing:

ListItemCache['CommentData' | 'DeviceData' | 'UserData'] = (CommentData | DeviceData | UserData)[]

So tsc complains for that because ListItemCache['CommentData'] = DeviceData would be a possible combination which is wrong.

Related