What is the correct way to add properties to an interface in TypeScript?

I have experience with styled-components "themes" and how they work by defining custom theme properties for TypeScript autocompletion within styled components. The process involves extending the DefaultTheme interface with our custom theme properties like this:

import { MyTheme } from '~/configurations'

declare module 'styled-components' {
  interface DefaultTheme extends MyTheme {}
}

In the configuration file ./configurations/index.ts, we define the custom theme properties as follows:

const theme = {
  fonts: {
    font1: 'foo'
  },
  colors: {
    red: 'bar'
  }
}

export type MyTheme = typeof theme

The DefaultTheme then dynamically inherits these properties from our custom theme, allowing us to type everything in styled-components according to our theme.

However, when trying to implement similar functionality from scratch, I encountered some issues. Here is an example of a TypeScript library I created, called Halt.js:

import { CustomError } from 'ts-custom-error'

// Rest of the code...

In the Halt.js library, there is a HaltList that I want to inject dynamic properties into to enable TypeScript autocompletion:

halt('

To achieve this, I define custom error codes in a separate file (./configurations/errors) and attempt to extend the HaltList interface:

import { HaltType } from './configurations/errors'

declare module '@lancejpollard/halt.js' {
  export interface HaltList extends HaltType {}
}

Despite my efforts, the autocomplete feature doesn't seem to be working properly. How can I ensure that the extended HaltList interface includes all the custom properties, such as missing_property, and throws errors for undefined properties?

Update

My usage scenario involves defining custom error codes in Halt.js and using them in other files:

// Code snippet

Although autocompletion works while writing code, assigning the custom properties to Halt.list generates an error. To resolve this, I tried explicitly defining HALT as HaltList:

import { Halt, HaltList } from "@lancejpollard/halt.js";

const HALT: HaltList = {
// ... remaining code

Unfortunately, this caused the autocomplete feature to break with an error message stating:

Argument of type 'string' is not assignable to parameter of type 'never'.

This issue occurs at the location of halt('missing_property').

Answer №1

When you enter the following code:

type a = HaltList['missing_property']
//   ^?

You are ensuring that the type merging functions correctly (if not, provide feedback in the comments)

The issue lies in simply using

keyof (Record<string, V> & { k: V1 })
as it will only return string

To obtain the desired keyof rather than the current one, you must eliminate the index signatures:

https://tsplay.dev/Nr93zw

// import type { OmitIndexSignature } from 'type-fest' // doesn't work on Playground, so
type OmitIndexSignature<ObjectType> = {
    [KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
        ? never
        : KeyType]: ObjectType[KeyType];
};

export default function halt(
  form: keyof OmitIndexSignature<HaltList>
) {}

type a = Exclude<keyof HaltList, 'missing_property'>
//   ^? type a = string | number
type b = HaltList['missing_property']
//   ^? type b = { code: number; note: string; }

halt('')
//    ^| missing_property

////////////////////////////////////////////////
// file A.ts
const HALT = {
  missing_property: {
    code: 1,
    note: 'Property missing',
  },
}
export type HaltType = typeof HALT
// declare module '@me/lib/B.ts'
  export interface HaltList extends HaltType {}
// }
////////////////////////////////////////////////
// fine B.ts
export type HaltHook = {
  code: number
  hint?: string | ((link: URL) => string)
  note: string | ((link: URL) => string)
  term?: Array<string> | ((link: URL) => Array<string>)
}
export interface HaltList {
  [key: string]: HaltHook
}
////////////////////////////////////////////////

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

Sharing data from a Provider to a function in React can be done through various methods

After developing an NPM library that contains various utility functions, including one for calling endpoints, I encountered a roadblock when trying to set the Axios.create instance globally. Initially, my idea was to create a Provider and establish a cont ...

"Implementing Ionic 2 tabs allows for retrieving the previously selected option with the

Here is the code I am currently working on: onTabsChange(abc) { let selected_tab = this.tabs.getSelected(); let tab_index = selected_tab.index; console.log(tab_index); // should print current tab index but it prints previously selected tab index ...

Listen for incoming data from the client in the form of an ArrayBuffer

I have been utilizing the ws library within nodejs to develop a small cursor lobby where players can interact. I have managed to utilize the server to send ArrayBuffers with bit streams to the client and successfully decode them. However, I am encountering ...

Encountering a discord bot malfunction while on my Ubuntu server

My discord bot runs smoothly on my Windows 10 setup, but when deployed to the server running Ubuntu Server 20, it encounters a specific error. The issue arises when trying to handle incoming chat messages from the server. While I can read and respond to m ...

No pipe named '' was discovered

I have created a custom pipe in Angular, but when I try to use it, I keep receiving the error message: "No pipe found with name 'RefPipe'". I have searched for solutions online and they all suggest importing the pipe. However, I have tried import ...

Strange problem encountered when transferring data to and from API using Typescript and Prisma

I'm encountering a strange issue that I can't quite pinpoint. It could be related to mysql, prisma, typescript, or nextjs. I created the following model to display all product categories and add them to the database. Prisma Model: model Product ...

Implementing the binding of components to another component post using forwardRef

I am looking to connect the Title and Body components with the Wrapper component. Previously, my component structure looked like this: import { FC } from 'react'; // binding required components const Title: FC<...> = () => {...} const ...

Angular: ChangeDetection not being triggered for asynchronous processes specifically in versions greater than or equal to Chrome 64

Currently, I'm utilizing the ResizeObserver in Angular to monitor the size of an element. observer = new window.ResizeObserver(entries => { ... someComponent.width = width; }); observer.observe(target); Check out this working example ...

Using jest-dom without Jest is definitely an interesting challenge that many developers may

Can anyone help me with extending Typescript interfaces? I have come across a situation that I am trying to solve. In my tests, I am utilizing expect without using Jest directly (I installed it separately and it functions properly). Now, I am interested ...

Increasing a value within HTML using TypeScript in Angular

I'm working with Angular and I have a TypeScript variable initialized to 0. However, when trying to increment it using *ngFor in my .ts file, the increment is not happening (even though the loop is running correctly). my-page.html <div *ngFor=&quo ...

Error message in TypeScript: A dynamic property name must be a valid type such as 'string', 'number', 'symbol', or 'any'

Attempting to utilize the computer property name feature in my TypeScript code: import {camelCase} from "lodash"; const camelizeKeys = (obj:any):any => { if (Array.isArray(obj)) { return obj.map(v => camelizeKeys(v)); } else if (ob ...

type of key extractor is unknown in React Native

How can I specify a type for the renderItem function of a FlatList in React Native? This is my current approach: // Importing the generic type for the FlatList render item function import { ListRenderItem } from "react-native"; // Assigning the ...

Ways to convert a callback-based function into a promise without losing the returned value

After being given access to this API: function doSomeWork(callbacks : { success ?: (result : SuccessCallbackResult) => void, fail ?: (result : FailCallbackResult) => void, complete ?: (result : CompleteCallbackResult) => void }) : Task ...

I'm confused about how to make sure each child in a list has a distinct 'key' prop, it's important for proper rendering

Hey there! So I've got this assignment from school that involves fetching data from randomuser.me. I followed all the steps given to me, but ran into an issue when a warning popped up in the terminal. The project compiled successfully and is running f ...

What is the best method for compressing and decompressing JSON data using PHP?

Just to clarify, I am not attempting to compress in PHP but rather on the client side, and then decompress in PHP. My goal is to compress a JSON array that includes 5 base64 images and some text before sending it to my PHP API. I have experimented with l ...

What is the best way to interpret the call signature under these circumstances?

I'm new to TypeScript and a bit confused about the call signature concept. In the code snippet below, there's an interface named Counter that is supposed to represent a function type with additional properties like interval and reset. However, I& ...

Utilizing Typescript generics to define constraints for the type of T[K], where K is the key and T is the object

In the setting I'm dealing with, specific objects with an id attribute expire every "tick" and require retrieval using getObjectById. I am interested in creating a setter function to update a property of an object by mapping thing.property => getOb ...

Angular functions are executed twice upon being invoked within the html file

I decided to kick-start an Angular project, and I began by creating a simple component. However, I encountered a perplexing issue. Every time I call a function in the HTML file from the TypeScript file, it runs twice. TS: import { Component, OnInit } from ...

Updating from Angular version 12.0.4 to 12.1.0 results in a runtime error. As a temporary solution, we are reverting back to version 12.0

There is a related issue discussed here: Angular: TypeError: Cannot read property 'firstCreatePass' of null However, the problem in that case pertains to different Angular versions and the solution provided did not resolve my issue. The recurring ...

Compiler unable to determine Generic type if not explicitly specified

Here is a simple code snippet that I am working with: class Model { prop1: number; } class A<TModel> { constructor(p: (model: TModel) => any) {} bar = (): A<TModel> => { return this; } } function foo<T>(p: ...