0

I'm trying to write a function that will accept 2 parameters i.e key and subkey and return one of the properties of that nested object. I have successfully written the type for function but cannot access the property for the same.

This is what my code looks like:

type ObjectDefinationType<T> = {
  myFunction: () => void;
  data?: T[];
  someData: string;
};

type KeySet1 = "key1" | "key2";
type KeySet2 = "key3" | "key4";

type DataType1 = {
  id: string;
  num: number;
};

type DataType2 = {
  age: number;
  address: string;
};

type KeySet1Record = Record<KeySet1, ObjectDefinationType<DataType1>>;
type KeySet2Record = Record<KeySet2, ObjectDefinationType<DataType2>>;

type SetStoreType = {
  set1: KeySet1Record;
  set2: KeySet2Record;
};

const initialData = {
  myFunction: () => {},
  someData: "someData"
};
const set1: KeySet1Record = {
  key1: { ...initialData },
  key2: { ...initialData }
};
const set2: KeySet2Record = {
  key3: { ...initialData },
  key4: { ...initialData }
};

const setStore: SetStoreType = {
  set1,
  set2
};

const myFunc = <T extends keyof SetStoreType, K extends keyof SetStoreType[T]>(
  key: T,
  subKey: K
) => {
  // This should also work
  console.log("I want to access data", setStore[key][subKey].someData);

  // This will work but I dont want this:
  // setStore[key][subKey] = {
  //   ...setStore[key][subKey],
  //   myFunction: () => {
  //     // This is some function
  //   }
  // };

  // This should work:
  setStore[key][subKey].myFunction = () => {
    // This is some function
  };
};

I am not sure why I am not able to access

myFunction property from an object

I have created a code sandbox for the same Code sandbox

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
Kunal Jain
  • 41
  • 4
  • I don't see the problem here, I am able to set both the properties and call the function and log the other property in the code sandbox, [check this sandbox](https://codesandbox.io/s/pensive-darwin-9g3utp) – nullptr Jun 28 '22 at 16:37

2 Answers2

1

I'm not smart enough with Typescript to figure out why what you want to do does not seem to be working, but you could work around this behavior by using a helper function employing type-fest's Get utility type and Lodash's get function like so: https://codesandbox.io/s/compassionate-sammet-w4hr53?file=/src/App.tsx

Not sure if that is usable for whatever it is that you need, but it's the best I could come up with.

JamesK
  • 106
  • 4
  • This has to do with TS inference engine trying to be efficient. When it tries to parse `setStore[key][subKey]` it will parse all possible combinations and union them, such as `setStore[set1][key1]`, but will also do incorrect ones, such as `setStore[set1][key4]`. This is because it hasn't narrowed type `T`, it is still `"set1" | "set2"`, thus K can be key1 through key4. `setStore[set1][key4]` is `unknown` and anything (except `any`) unioned with `unknown` also becomes `unknown`. Type-fest's `Get` type forces the compiler to go step by step, narrowing the object correctly. – Cody Duong Jun 30 '22 at 00:28
  • But when we are calling the methods, it gives options correctly, so if I write set1 we actually get key3 and key4 as second option and does not allow key1 and key2. So that TS is actually able to narrow it down. So why it is not allowing to get any values from it while implementation – Kunal Jain Jul 21 '22 at 10:12
  • Hi @jamesk , Your solution solves the problem but then we have an overhead of including few lib. Is there any alternative to this which reduces use of any external lib – Kunal Jain Jul 21 '22 at 10:34
  • Hi @KunalJain . I recommended Lodash because I couldn’t seem to solve the problem without it. I know Lodash is a huge library, but they also offer their functions as standalone modules which significantly cuts down on bloat. The only solution I personally could find to your issue was the one I recommended, where I used typefest and Lodash. I’m certain you could reimplement the necessary types/functions from these packages if you really need to be uber-minimalist when it comes to dependencies in your project, maybe try that? Sorry. I don’t have a better answer for you. – JamesK Jul 22 '22 at 13:39
  • @CodyDuong ‘s comment may be the key to a dependency-free/minimal dependencies solution in your case tho. I’m not as familiar with typescript compiler internals as they seemed to be so I don’t know what advice to give based off their comment, but they seem to be confident in their analysis. Maybe try and wrap your head around how Typefest’s Get type has it’s described effect on “making the compiler go step by step”, and refactor your code / rewrite your functions to try and emulate or take advantage of that behaviour without using the library itself? Just spitballing here. Best of luck – JamesK Jul 22 '22 at 13:45
0

Instead of using generic types you can use the actual types.

type KeysOfUnion<T> = T extends T ? keyof T : never;
const myFunc = (key: keyof SetStoreType, subKey: KeysOfUnion<SetStoreType[keyof SetStoreType]>) => {
    setStore[key][subKey].myFunction = () => {
        // This is some function
    };
    console.log("I want to access data", setStore[key][subKey].someData);
};

I got the small trick to get key of union from here.

Okan Aslan
  • 127
  • 5