So, I initially wanted to do the following conversion:
import { ComponentType } from 'react';
import { Component1, Component2 } from './components';
const components = {
foo: Component1,
bar: Component2,
};
// Into
const componentLookup = {
foo: {
name: 'foo',
component: Component1
},
bar: {
name: 'bar',
component: Component2
},
};
So, I created the following TS logic:
const components = {
foo: Component1,
bar: Component2,
};
type ComponentName = keyof typeof components;
type ComponentLookup = {
[key in ComponentName]: {
name: ComponentName;
component: ComponentType;
};
};
let componentLookup: ComponentLookup = {} as ComponentLookup;
(Object.keys(components) as ComponentName[]).forEach((name) => {
componentLookup[name] = { name: name, component: components[name] };
});
// Intellisense picks up keys
componentLookup.foo;
componentLookup.bar;
Finally, I decided I want to create a function createLookups
to take the components object and perform the logic instead, however, intellisense is having trouble.
const createLookups = (components: {
[name: string]: ComponentType;
}): {
[key in keyof typeof components]: {
name: keyof typeof components;
component: ComponentType;
};
} => {
type ComponentName = keyof typeof components;
type ComponentLookup = {
[key in ComponentName]: {
name: ComponentName;
component: ComponentType;
};
};
let componentLookup: ComponentLookup = {} as ComponentLookup;
(Object.keys(components) as ComponentName[]).forEach((name) => {
componentLookup[name] = { name: name, component: components[name] };
});
return componentLookup;
};
If createLookups
is defined in the same file and I call createLookups(components)
, intellisense picks up on foo
and bar
; however, if createLookups
is defined in another file, it does not pick up on the foo
/bar
keys readily.
Is this an issue with TypeScript or my editor (WebStorm)?
CodePudding user response:
If you solve this problem in a general way (map the entries (key-value pairs) of an object to arbitrary property names), then you can apply it to your specific need (mapping property names and Component values), using a combination of currying and generic parameter constraint:
import {type ComponentType} from 'react';
type MappedEntries<
ObjectType extends Record<string, unknown>,
KeyProp extends string,
ValueProp extends string,
> = {
[K in keyof ObjectType]: (
Record<KeyProp, K>
& Record<ValueProp, ObjectType[K]>
);
};
function mapEntriesToKeysInObjects <
ObjectType extends Record<string, unknown>,
KeyProp extends string,
ValueProp extends string,
>(obj: ObjectType, keyProp: KeyProp, valueProp: ValueProp): MappedEntries<ObjectType, KeyProp, ValueProp> {
return (Object.keys(obj) as (keyof ObjectType)[]).reduce((mapped, key) => {
mapped[key] = {
[keyProp]: key,
[valueProp]: obj[key],
} as Record<KeyProp, keyof ObjectType> & Record<ValueProp, ObjectType[keyof ObjectType]>;
return mapped;
}, {} as MappedEntries<ObjectType, KeyProp, ValueProp>);
}
// Let's test it:
const mapped = mapEntriesToKeysInObjects({first: 1, last: 2}, 'name', 'component');
mapped.first.name; // "first"
mapped.first.component; // number
mapped.last.name; // "last"
mapped.last.component; // number
// Looks good
// Now apply it to your function:
const createLookups = <T extends Record<string, ComponentType>>(components: T) => {
return mapEntriesToKeysInObjects(components, 'name', 'component');
};