Home > Enterprise >  How to convert the following TypeScript logic into a function?
How to convert the following TypeScript logic into a function?

Time:03-08

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:

TS Playground

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');
};

  • Related