Uncovering the origins of computed object keys in TypeScript

I am currently working on a project where I need to easily define and use new plugins using TypeScript in my IDE. My folder structure looks like this:

src
│ ...
└── plugins
   └── pluginA
   |     index.ts
   └── pluginB
   |     index.ts
   └── ...
     index.ts <-- "here I want to combine all plugins in one object"

Each plugin has a default export in index.ts with an interface as follows:

interface Plugin {
  name: string
  ...
}

src/plugins/pluginA/index.ts

export default {
  name: "pluginA",
  ...
}

src/plugins/index.ts

import pluginA from "./pluginA"
import pluginB from "./pluginB"
...

export default {
  [pluginA.name]: pluginA,
  [pluginB.name]: pluginB,
  ...
}

I aim for TypeScript to be able to understand the names of existing plugins (to infer the Literal type of keys of the plugins).

This means that when using plugins in my code, I want it to look something like this:

import plugins from "src/plugins"

...

const plugin = plugins["nonExistingPlugin"] // TypeScript should show error here if no plugin exists with this name

const anotherPlugin = plugins["plug_"] // I expect the IDE to autocomplete the name of the plugin from the existing ones
//                            "pluginA"
//                            "pluginB"
//                            "..."

All my attempts so far have led me to TypeScript recognizing the `name` property of the plugin as a `string`, but not inferring a Literal type.

Is there a way for TypeScript to achieve this? And if not, are there any other methods to accomplish this goal?

Answer №1

You have the option to utilize as const assertions in this scenario:

const addonA = {
  name: 'addonA',
  //
} as const
const addonB = {
  name: 'addonB',
  //
} as const
const addonC = {
  name: 'addonC',
  //
} as const

const addons = {
  [addonA.name]: '',
  [addonB.name]: '',
  [addonC.name]: '',
}
const addon = addons["nonExistingAddon"] 

// Expected type error occurs
// Element implicitly has an 'any' type due to inability to index type '{ addonA: string; addonB: string; addonC: string; }' with expression of type '"nonExistingAddon"'.
//  Property 'nonExistingAddon' does not exist on type '{ addonA: string; addonB: string; addonC: string; }'.ts(7053)
const addon = addons["addon"] // Autocomplete options for `addonA`, `addonB`, `addonC` are displayed by the Editor

If you also wish to ensure that each addon adheres to a specific Addon type, you can implement the following validation code:

type Addon = {
  name: string
  // other details
}

// An error will be triggered if any of your addons do not match the Plugin type
export const _addonsTypeCheck: {
  [key: string]: Addon
} = addons

Answer №2

Consider utilizing the enum method to achieve this:

types.d.ts

export enum PluginNames {
   PluginA = 'pluginA',
   PluginB = 'pluginB',
   PluginC = 'pluginC',
}

interface Plugin {
   name: PluginNames
}

src/plugins/pluginA/index.ts

const pluginA: Plugin = {
  name: PluginNames.PluginA
}

export default pluginA

src/plugins/index.ts

import pluginA from "./pluginA"
import pluginB from "./pluginB"
...

export default {
  [PluginNames.PluginA]: pluginA
  [PluginNames.PluginB]: pluginB
  ...
}

You can also accomplish the same outcome by using type unions:

interface Plugin {
   name: 'pluginA' | 'pluginB' | 'pluginC'
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When using TypeScript and React with Babel, an error may occur: "ReferenceError: The variable 'regeneratorRuntime'

I've been delving into learning typescript, react, and babel, and here is the code I've come up with: export default function App(): JSX.Element { console.log('+++++ came inside App +++++++ '); const {state, dispatch} = useCont ...

Encountering "No overload matches this call" error in Typescript while working with fetched data and material-ui

Before attempting to create a dropdown menu with an array retrieved using useSWR, I first practiced creating one with a hardcoded array. I used this for the initial practice: https://codesandbox.io/s/76k0ft?file=/demo.tsx:1819-2045 Instead of using a hard ...

Exploring the Power of SectionList in Typescript

How should SectionList be properly typed? I am encountering an issue where this code works (taken from the official documentation example): <SectionList renderItem={({item, index}) => <Text key={index}>{item}</Text>} renderSectionHea ...

Disabling eslint does not prevent errors from occurring for the unicorn/filename-case rule

I have a file called payment-shipping.tsx and eslint is throwing an error Filename is not in camel case. Rename it to 'paymentShipping.tsx' unicorn/filename-case However, the file needs to be in kebab case since it's a next.js page that s ...

typescript optimizing initial load time

When importing the npm package typescript, it typically takes around 0.3 seconds. Is this considered an acceptable load time? Are there any steps that can be taken to optimize performance? The code snippet in index.js demonstrates the process: import &apo ...

Tips for updating the secure route with TypeScript and React-Router versions 4, 5, or 6

I've been attempting to create a <PrivateRoute> based on the example in the react-router documentation using TypeScript. Can someone provide assistance? The PrivateRoute from the react-router documentation: const PrivateRoute = ({ component: Co ...

Quick tip: Adding a close 'X' button to an ng-bootstrap popover

As a newcomer to angular 5, I have been working on adding an 'x' button in the top right corner of a popover. Once this 'x' is clicked, the popover should be closed. Is there a way to achieve this using ng-bootstrap popover? Below is my ...

Using ReactJS with Material UI and applying styles with withStyles including themes in TypeScript

I've been working on converting the Material UI Dashboard into TypeScript. You can find the original code here: Material UI Dashboard One issue I'm encountering is that I am unable to define CSS styles within the withStyles function while export ...

Apply criteria to an array based on multiple attribute conditions

Given an array containing parent-child relationships and their corresponding expenses, the task is to filter the list based on parents that have a mix of positive and negative expenses across their children. Parents with only positive or negative child exp ...

What are the steps to set up tsline in settings.json file?

Currently utilizing visual studio code Upon searching for the settings.json file, the contents appear as follows: { "liveServer.settings.donotVerifyTags": true, "liveServer.settings.donotShowInfoMsg": true, "javascript ...

What could be causing the Typescript error when utilizing useContext in combination with React?

I am currently working on creating a Context using useContext with TypeScript. I have encapsulated a function in a separate file named MovieDetailProvider.tsx and included it as a wrapper in my App.tsx file. import { Context, MovieObject } from '../in ...

Transfer dynamically generated table data to the following page

Seeking guidance on a common issue I'm facing. I am creating a table using data from Firebase upon page load, and I want users to click on a row to view specific details of that item. It may sound confusing, but it will make more sense with the code p ...

Angular BreakPointObserver is a powerful tool that allows developers

Hey there! I've been working with the BreakpointObserver and have run into an issue while trying to define breakpoints for mobile and tablet devices. It seems that my code is functioning properly for tablets, but not for mobile devices. Upon further i ...

Utilizing a directive in contexts beyond a component

I have developed a popover module that consists of two components and three directives. However, I am encountering an issue where I am unable to utilize the directives outside of the main component. Whenever I try to do so, I face an editor error stating: ...

Capturing a webpage through Record RTC integration with Angular

I am looking to record a specific section of the page as a video with audio. For example, when a user enters the page, I want it to automatically start recording the activities in that particular area (similar to how iframe videos work). The recording sh ...

The occurrence of a loading error arises when attempting to load the second component, displaying the message 'The template instructed for component SidebarComponent is

My journey with Angular has just begun, and I decided to challenge myself by creating a simplistic dashboard. In order to achieve this, I developed two components called DashboardComponent and SidebarComponent. The DashboardComponent loads smoothly witho ...

Leveraging the Angular (2) routerLinkActive directive to handle dynamic routes

Although my current approach works, I believe there may be a more efficient way to implement this in Angular. The situation is as follows: Imagine nested, inflected paths like /logos and /logo/:id The markup below functions as intended: <li class ...

The error message "tsc not found after docker image build" appeared on the

My goal is to deploy this program on local host. When I manually run "npm run build-tsc," it works successfully. However, I would like Docker to automatically run this command when building the image. Unfortunately, I receive an error saying that tsc is no ...

Next.js v13 and Firebase are encountering a CORS policy error which is blocking access to the site.webmanifest file

Background: I am currently developing a website using Next.js version 13 in combination with Firebase, and I have successfully deployed it on Vercel. Upon inspecting the console, I came across two CORS policy errors specifically related to my site.webmani ...

Issue with Angular UI Bootstrap accordion heading behavior when used in conjunction with a checkbox is causing

I have implemented a checkbox in the header of an accordion control using Bootstrap. However, I am facing an issue where the model only updates the first time the checkbox is clicked. Below is the HTML code for the accordion: <accordion ng-repeat="tim ...