Ensure that only one property is mandatory in a Typescript interface, while permitting the rest to be

In my project, I am developing a WebSocket component that requires the type Message to have a mandatory property named type.

export type Message = {
  type: string;
  [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};

This component is configured with a set of callback functions that are triggered whenever a Message of the corresponding type is received.

export interface Listeners {
  [type: string]: (msg: Message) => void;
}

Here's a snippet of the WebSocket implementation code:

...
ws.onmessage = (event: MessageEvent): void => {
  console.log("[websocket] got raw message", event.data);
  try {
    const msg = JSON.parse(event.data);

    if (listeners[msg.type]) {
      console.log("[websocket] got message", msg);
      listeners[msg.type](msg);
    }
  }
}
...

When utilizing the WebSocket component, I aim to define a custom type as an extension of the base Message type, which includes the type property.

interface GraphMessage extends Message {
  id: string;
  type: "initial" | "update";
  chartType: "line" | string;
  data: GraphPoint[];
}

I want to use the component in the following way:

const handleUpdate = (msg: GraphMessage) => {}
const handleInitial = (msg: GraphMessage) => {}

const ws = await websocket("ws://localhost:9999/", {
  initial: handleInitial,
  update: handleUpdate
});

Unfortunately, I am encountering a Typescript error:

TS2322: Type '(msg: GraphMessage) => void' is not assignable to type '(msg: Message) => void'. 
  Types of parameters 'msg' and 'msg' are incompatible. 
    Type 'Message' is not assignable to type 'GraphMessage'.

To resolve this issue, I believe making Message a generic type would be beneficial.

type Message<T> = {
    type: string
    [key: string]: any
} & T

interface GraphMessage {
    graphName: string
}

type Callback = (msg: Message<GraphMessage>) => void

const myBaseMessage = {
    t...

Playground Link

Answer №1

It is important to recognize that TypeScript raises a valid point by stating that

Type '(msg: GraphMessage) => void' is not assignable to type '(msg: Message) => void'
.

When your handler relies on the specific structure of GraphMessage but you only provide a generic Message, there is a risk of encountering runtime exceptions:

// The 'id' property may or may not be included
export type Message = {
    type: string;
    [key: string]: any;
};

interface GraphMessage extends Message {
  id: string;
  type: "initial" | "update";
}

const message: Message = {
    type: 'foo',
}

const handler = (msg: GraphMessage): void => {
    console.log(msg.type.toLocaleLowerCase());
}

Attempting to call

handler(message); // will result in a compile-time error

By using TypeScript, potential exceptions can be caught before execution.

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

React Typescript: The specified argument type cannot be assigned to the parameter type

Creating a Check Box Button React component has resulted in an error related to setRSelected(1) and setRSelected(2)... const [cSelected, setCSelected] = useState([]); const [rSelected, setRSelected] = useState(); const onCheckboxBtnClick = (selected ...

The error message "Cannot bind to 'ngForOf' because it is not recognized as a valid property of the element."

After utilizing NGFor for quite some time, I encountered an unexpected issue in a new application where I received the error message: Can't bind to 'ngForOf' since it isn't a known property of 'div'.' I found it strang ...

What is the significance of needing to add a question mark to the TypeScript `never` type when working with

This piece of code effectively enforces the conditional requirement for the isDirty field to be included in a lecture object: If the id is a string, then an isDirty field must be added to the object: If the id is a number, then the object cannot have an i ...

Creating TypeScript models from a JSON response in React components

My Angular 2 application retrieves a JSON string, and I am looking to map its values to a model. According to my understanding, a TypeScript model file is used to assist in mapping an HTTP Get response to an object - in this case, a class named 'Custo ...

What is the necessity behind keeping makeStyles and createStyles separate in Material UI with TypeScript?

Dealing with TypeScript issues in Material UI has been a frequent task for me, and I find it frustrating that styling components requires combining two different functions to create a hook every time. While I could use a snippet to simplify the process, it ...

The element is implicitly assigned to an 'any' type due to the inability to use a 'string' type expression to index the 'Breakpoints' type

I have a question related to TypeScript that I need help with. My objective is to create a custom hook for handling media queries more efficiently. Instead of using useMediaQuery(theme.breakpoints.down('md');, I want to simplify it to: useBreakP ...

Reading text files line by line in TypeScript using Angular framework is a valuable skill to have

Struggling with reading text files line by line? While console.log(file) may work, it doesn't allow for processing each individual line. Here's my approach: In api.service.ts, I've implemented a function to fetch the file from the server: ...

Save visuals in the typescript distribution folder

Is there a way to properly include images in a Typescript project? I attempted to include the files or directory in tsconfig.json, but it didn't seem to work as expected. "include": [ "src/resources/**/*.png", // <- ...

Include a parameter in a type alias

Utilizing a function from a library with the following type: type QueryFetcher = (query: string, variables?: Record<string, any>) => Promise<QueryResponse> | QueryResponse; I aim to introduce an additional argument to this type without mod ...

How to resolve TypeScript error TS2322 when a function returns an interface

export interface AWSTags { CreatedBy: string; Environment: EnvironmentMap; Name: string; OwnedBy: string; Platform: string; Product: string; Runbook: string; Service: string; } Another script contains the following function to generate an ...

An error has occurred in the Next.js App: createContext function is not defined

While developing a Next.js application, I keep encountering the same error message TypeError: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function every time I try to run my app using npm run dev. This issue arises when attempting to co ...

How to Reload the Active Tab in Angular 5?

In my project, I have noticed that a listener in one of the tabs causes the entire tab to refresh, resulting in it displaying information from the first tab until I switch to another tab and then go back. 1) Is there a way to refresh only the current tab? ...

The presence of HttpInterceptor within a component is causing a ripple effect on all of the App

I am encountering an issue with a library I have that includes a component. This component has an HttpInterceptor that adds a header to each of its requests. The problem arises when I use the component in another project - the HttpInterceptor ends up addi ...

What sets apart a JSON Key that is enclosed in double quotes "" from one that has no quotes?

Below is my TypeScript object: { first_name:"test", last_name: "test", birthdate:"2018-01-08T16:00:00.000Z", contactNumber: "12312312312", email:"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e19 ...

Questioning the syntax of a TypeScript feature as outlined in the documentation

I have been studying the Typescript advanced types documentation, available at this link. Within the documentation, there is an example provided: interface Map<T> { [key: string]: T; } I grasp the concept of the type variable T in Map. However ...

Fresh React framework

I haven't worked on a React app in a while, but when I decided to start a new one and import my old function, I encountered the following error: C:/Users/Hello/Documents/Dev/contacts/client/src/App.tsx TypeScript error in C:/Users/Hello/Documents/Dev ...

The object contains an incorrect property that shouldn't be there

I am currently working on an application using NestJS with the Fastify adapter But I am encountering something strange with object construction. Going through all the related classes and methods: Controller endpoint handler @Get() @ApiOperation ...

On the subsequent iteration of the function, transfer a variable from the end of the function to the beginning within the same

Can a variable that is set at the end of a function be sent back to the same function in order to be used at the beginning of the next run? Function function1 { If(val1.id == 5){ Console.log(val1.id val1.name) } else{} Val1.id = 5 Val1.name = 'nam ...

Sharing FormikProps between components in React: A step-by-step guide

I am struggling to pass the necessary values and props that Formik requires to the component one level up. My approach involves using several small components for various forms, and I need to pass them through a complex component to be able to pass them do ...

Defined interface with specific number of members

I am tasked with creating an interface that has only two members, one of which is optional and both have undefined names. Something like: interface MyInterface { [required: string]: string|number [optional: string]?: string|number } Unfortunately, ...