easy-peasy: Can't use index signature types in model with generics

Related issue: https://github.com/ctrlplusb/easy-peasy/issues/117

I’m using "easy-peasy": "2.3.0" and "typescript": "3.3.4000".

I have the following store, which produces a TS “error”:

interface IMyStoreModel<T extends IMyInterface> {
  myMap: {
    [id: string]: T;
  };
  values: Select<IMyStoreModel<T>, Array<T>>;
}

const generateModel = <T extends IMyInterface>() => {
  const myStore: IMyStoreModel<T> = {
    myMap: {},
    values: select(state => 
      Object.keys(state.map)
        //                  👇 error
        .map(key => state.myMap[key])),
  }
  return myStore;
}

If I define IMyStoreModel without using generic types, the error disappears:

// works
interface IMyStoreModel {
  myMap: {
    [id: string]: IMyInterface;
  };
  values: Select<IMyStoreModel, Array<IMyInterface>>;
} 

I could also cast state.myMap to its correct type - but that gets quite ugly.

Here is the exact error message produced:

(property) myMap: Compact<{ [K in (T extends Select<any, any> | Reducer<any, any> ? string : never) | (T extends Select<any, any> | Reducer<any, any> ? number : never) | Exclude<Exclude<…>, (T extends Select<…> | Reducer<…> ? string : never) | (T extends Select<…> | Reducer<…> ? number : never)> | Exclude<…>]: { [P in keyof Compact<…>]: Compact<…>[P]; }[K]; } & Compact<…>> Element implicitly has an ‘any’ type because type ‘Compact<{ [K in (T extends Select<any, any> | Reducer<any, any> ? string : never) | (T extends Select<any, any> | Reducer<any, any> ? number : never) | Exclude<Exclude<…>, (T extends Select<…> | Reducer<…> ? string : never) | (T extends Select<…> | Reducer<…> ? number : neve…’ has no index signature.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 24 (11 by maintainers)

Most upvoted comments

Also tried upgrading TS to 3.5.1 - but the issue is still present there 😢

It’s also compatible with typescript@3.3.4000 FYI

also easy-peasy@2.3.1-alpha.3 worked like a charm. Seems like there was some issues with easy-peasy@2.3.1-alpha.2 - even got errors with useStore hooks. All kinks gone after upgrading to alpha 3 👍

It seems to be working, but there is something wierd with selectors - it does not seem to be part of the state object? 🤔

Updated testcase for you:

interface IMyInterface {
  name: string;
}

interface IMyInterfaceMap<T extends IMyInterface> {
  [id: string]: T;
}

interface IMyStoreModel<T extends IMyInterface> {
  myMap: IMyInterfaceMap<T>;
  values: Select<IMyStoreModel<T>, Array<T>>;
  names: Select<IMyStoreModel<T>, string>;
  setState: Action<IMyStoreModel<T>, IMyInterfaceMap<T>>
}

const generateModel = <T extends IMyInterface>(): IMyStoreModel<T> => {
  return {
    myMap: {},
    values: select(state => {
      return Object.keys(state.myMap).map(key => state.myMap[key]);
    }),
    names: select(state => {
      //           👇 error
      return state.something.map(x => x.name);
    }),
    setState: action((state, newMap) => {
      state.myMap = newMap;
    })
  };
};

interface IMyExtendedInterface extends IMyInterface {
  extended: boolean;
}

const store = createStore(generateModel<IMyExtendedInterface>());

const foo = Object.values(store.getState().myMap);
foo[0].name = 'bob';
foo[0].extended = true;

store.getState().myMap['foo'] = { name: 'bob', extended: true };

store.getState().myMap['foo'].name + 'foo';

Error:

Property ‘something’ does not exist on type ‘Compact<{ myMap: IMyInterfaceMap<T>; } & Compact<{} & {}>>’.

I’m guessing selectors is supposed to be part of the first Compact-thingy?

@jmyrland - 3rd times a charm.

npm i easy-peasy@2.3.1-alpha.2

Testing ATM - it seems that accessing the state is working, but setting the state is not. (TS 3.3.4000)

I have updated your test case to account for this as well:

interface IMyInterface {
  name: string;
}

interface IMyInterfaceMap<T extends IMyInterface> {
  [id: string]: T;
}

interface IMyStoreModel<T extends IMyInterface> {
  myMap: IMyInterfaceMap<T>;
  values: Select<IMyStoreModel<T>, Array<T>>;
  setState: Action<IMyStoreModel<T>, IMyInterfaceMap<T>>
}

const generateModel = <T extends IMyInterface>(): IMyStoreModel<T> => {
  return {
    myMap: {},
    values: select(state => {
      return Object.keys(state.myMap).map(key => state.myMap[key]);
    }),
    setState: action((state, newMap) => {
      // 👇 error
      state.myMap = newMap;
    })
  };
};

interface IMyExtendedInterface extends IMyInterface {
  extended: boolean;
}

const store = createStore(generateModel<IMyExtendedInterface>());

const foo = Object.values(store.getState().myMap);
foo[0].name = 'bob';
foo[0].extended = true;

store.getState().myMap['foo'] = { name: 'bob', extended: true };

store.getState().myMap['foo'].name + 'foo';

Error message:

Type ‘IMyInterfaceMap<T>’ is not assignable to type ‘{ [key: string]: T; } & { [P in keyof Pick<Pick<Compact<{ [K in Exclude<Exclude<string, T extends Action<any, any> | Listen<any, any, any> | Thunk<any, any, any, any, any> ? string : never>, T extends Select<any, any> | Reducer<…> ? string : never> | Exclude<…>]: Pick<…>[K]; } & Pick<…>>, { [K in keyof Compa…’.

No worries, TBH I have been sticking to 3.3.4000 as >=3.4 have introduced performance regressions with definitions of libraries such as styled-components. 😃

Sweet! Installed the alpha just now - and the error is gone! 👌 And I don’t have to type cast results with this change! 🎉

The current build of my project is currently unstable (failing tests due to breaking changes) - so I don’t know for certain if this change has any side effects - but as far as I can tell, it works as it should!

Wow, did not expect a resolution this quickly! Well done! 😁

I will give the alpha a shot and let you know how it works out in my case.

Yeah, I don’t recommend that approach as it’s not a JSON serialisable structure. You’ll definitely want the pure object form. 👍

Ah, as I suspected. I have seen this when I’ve modeled my stores with class instances. This might be a good thing to document in relation to “how to model stores” and advice to use serializable types 👍