Joy Biswas
Joy Biswas

Reputation: 6527

Typescript dynamic interface

I have a function I want to convert to generics Typescript to provide as an utility. I have reached it but what happens when a function returns a function. I know it is not required but still

/**
 * createConstants Type
 */
type createConstantsType =
  <T extends string, U extends string>(namespace: T, prefix: U | null) =>
    <V extends string>(...constants: V[]) => Record<V, string>;

/**
 * function for creating namespaced constants
 * @param {String} namespace the namespace for the constant to create
 * @param {String} prefix the prefix for the constant to create
 * @returns {Object} namespaced constants for a module/feature
 * 
 *    // Common approach
 *    export const NAMESPACE = 'manual';
 *    export const SIDEBAR_TOGGLE = `${NAMESPACE}/SIDEBAR_TOGGLE`;
 *    export const SIDEBAR_OPEN = `${NAMESPACE}/SIDEBAR_OPEN`;
 *    export const SIDEBAR_CLOSE = `${NAMESPACE}/SIDEBAR_CLOSE`;
 *
 *    // Usage of this utility
 *    export const NAMESPACE = 'manual';
 *    export const SIDEBAR = createConstants(NAMESPACE, 'sidebar')('TOGGLE', 'OPEN', 'CLOSE');
 *
 *    // which will generate:
 *    SIDEBAR = {
 *      TOGGLE: 'manual/SIDEBAR_TOGGLE',
 *      OPEN: 'manual/SIDEBAR_OPEN',
 *      CLOSE: 'manual/SIDEBAR_CLOSE',
 *    }
 * 
 */
export const createConstants: createConstantsType =
  <T extends string, U extends string>(namespace: T, prefix: U | null = null) =>
    <V extends string>(...constants: V[]): Record<V, string> => (
    constants.reduce((result: Record<V, string>, constant: string): Record<V, string>  => ({
      [constant.toUpperCase()]:
        `${namespace}/${(prefix) ? `${prefix.toUpperCase()}_` : ''}${constant.toUpperCase()}`,
      ...result,
    }), {} as Record<V, string>)
  );

Upvotes: 0

Views: 239

Answers (1)

Karol Majewski
Karol Majewski

Reputation: 25860

There are a few things to be aware of:

  • Don't use String when what you mean is string. The uppercase one is a reference to the global String object in JavaScript.
  • Avoid defining functions as Function. The definition of Function is very broad, and even if your function makes use of some precise types and generics, TypeScript will ignore all of that because it's told to treat it as just any Function. If you want to create a function type, write its signature this way: type MyFunction = (argument: number) => boolean instead.
  • As a rule of thumb, if your argument is of type string, you can start just by creating a type parameter (a generic) and saying it extends string. So, <T extends string>(argument: T) instead of (argument: string).

When you apply these rules, your solution will look like this:

export const createConstants = <T extends string, U extends string>(namespace: T, prefix: U | null = null) => <V extends string>(...constants: V[]): Record<V, string> => (
  constants.reduce((result, constant) => ({
    [constant.toUpperCase()]: 
      `${namespace}/${(prefix) ? `${prefix.toUpperCase()}_` : ''}${constant.toUpperCase()}`,
    ...result,
  }), {} as Record<V, string>)
);

const test = createConstants('manual', 'SIDEBAR')('TOGGLE', 'OPEN', 'CLOSE')

See TypeScript Playground.

Upvotes: 2

Related Questions