Exploring Typescript's ability to recursively deduce object attributes

Imagine having this AnalyticsService class

export class AnalyticsService {
  static sendAnalytics(eventName: string) {
    console.log(eventName);
    // logic here...
  }

  static EVENTS = {
    Header: {
      LogoClicked: "Header: Logo Clicked",
    },
    UserMenu: {
      LoginButtonClicked: "User Menu: Login Button Clicked",
      LogoutButtonClicked: "User Menu: Logout Button Clicked",
    }
  };
}

Using this class to send analytics like this:

AnalyticsService.sendAnalytics(AnalyticsService.EVENTS.Header.LogoClicked)

The goal is to gather all values of EVENTS into a union type to ensure that sendAnalytics function receives only valid event names

For instance, the expected results would be:

"Header: Logo Clicked" | "User Menu: Login Button Clicked" | "User Menu: Logout Button Clicked"

Is it feasible with typescript?

If so, will it have a notable impact on performance when dealing with a large object in TypeScript?

Edit: just to clarify the EVENTS object can be deeply nested (I provided a simple example for clarity)

Answer №1

If you want to correctly type the function sendAnalytics, follow these steps:

type Keys = typeof AnalyticsService.EVENTS[keyof typeof AnalyticsService.EVENTS]

type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type FlattenedEvents = UnionToIntersection<Keys>;

type EventNames = FlattenedEvents[keyof FlattenedEvents];

export class AnalyticsService {
  static sendAnalytics(eventName: EventNames) {
    console.log(eventName);
    // add your logic here...
  }

  static EVENTS = {
    Header: {
      LogoClicked: "Header: Logo Clicked",
    },
    UserMenu: {
      LoginButtonClicked: "User Menu: Login Button Clicked",
      LogoutButtonClicked: "User Menu: Logout Button Clicked",
    }
  } as const;
}

AnalyticsService.sendAnalytics(AnalyticsService.EVENTS.Header.LogoClicked); // This call is OK
AnalyticsService.sendAnalytics('test'); // This call will throw an error

Try it on TypeScript playground

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

Surprising Media Component Found in URL Parameters within the design

Exploring the page structure of my Next.js project: events/[eventId] Within the events directory, I have a layout that is shared between both the main events page and the individual event pages(events/[eventId]). The layout includes a simple video backgro ...

After successful sign-in, users will be redirected to the

After mainly working on mobile development, I am now diving into web development. I am currently utilizing firebase.auth() to sign in a user and I'm struggling with how to handle page redirection within my JavaScript/node application. What is the pro ...

What is the best way to position the list element to align with the top of a div container?

To see a live example, visit this link. My goal is to create a line that separates two li elements within a nested ul. However, the line ends up taking the width of the container ul instead of the div containing the entire structure. In the provided examp ...

Transform a string date in the format of YYYY-MM-DD into a Date object while maintaining the original date within the user's specific timezone

Currently, I am facing a challenge in trying to convert a string date formatted as YYYY-MM-DD into a date object that can be manipulated with matDatePicker. The issue that arises is that the date displayed always shows yesterday's date. After some inv ...

Issue with child component validation in VueJS 2 and vee-validate 3: not functioning as expected

Current vee-validate version: 3.4.5 I have a FormBuilder.vue component that constructs my form inputs schema accordingly. Within this component, I have a custom InputSlugify component and I am looking to implement vee-validate validation for it using the ...

Accessing ExpressJS from AngularJS through local server

My latest project involves building a Web Application using AngularJS and ExpressJS. In this application, I have set up a GET "/" route in my app.js file to render the index.html page when accessed. One interesting feature is the GET "/test-data" route in ...

A guide to implementing "Intl.DateTimeFormat" within a Next.js React component

I have a component that is being rendered on the server, making "Intl.DateTimeFormat" accessible. What is the proper way to use "Intl.DateTimeFormat" in a Next.js React component? The error message I am encountering is: Error: There was an error while hy ...

After the execution of the script by V8, which phase will be initiated first?

Scenario // test.js setTimeout(() => console.log('hello'), 0) setImmediate(() => console.log('world')) Simply execute node test.js using node v12.12.12 on an Intel MacBook Pro. The output may vary: hello world Sometimes it is: ...

Using the Vuex Module Decorator: A guide on accessing the store from a module

Recently, I've been playing around with the vuex module decorator in order to maintain good TypeScript practices in my vuex stores. However, I'm facing issues when trying to access the store without encountering type definition problems. My setu ...

Unleashing the Power of Google Apps Script Filters

Having some data in a Google Sheet, I am looking to filter it based on specific criteria and return corresponding values from another column. Additionally, I need to count the number of elements in the resulting column. Below is an example of the data: Sa ...

Attach to dynamically generated elements by event listeners that bind on mouseover or hover

I am looking to implement a hover effect for dynamically generated items in JavaScript. Specifically, when I hover over an icon element, I want a unique text to appear related to that icon: for (var i = 0; i < items.length; i++) { $('.items&ap ...

Error: Attempting to access properties of undefined object (reading 'hash') has caused an unhandled TypeError

I've been working on a project to store files in IPFS and then record the hash on the blockchain. However, I encountered an error message while trying to upload the file to IPFS. Error Message: Unhandled Rejection (TypeError): Cannot read properties ...

Node.js causing issues with retrieving data from REST Calls

I am trying to make a REST API call from my PHP and Node.js application to a specific URL provided by the client, which is expected to return a JSON object. While I am able to successfully retrieve data using PHP, I'm encountering issues with my Node ...

Access the state of a Vuex module within a different module's action

I'm feeling a bit lost when it comes to working with Vuex store components. How can I access the state of another module? I've tried various methods to retrieve data from the store, but I always end up with an Observer object. What is the corre ...

Axios fetch call consistently returns 404 error

Here is the GET request being made: this.props.userId holds the user ID. componentDidMount() { axios.get('/api/auth/booked/' + this.props.userId) .then(response => { console.log(response); }) .catch(error => ...

Having trouble retrieving data about my marker through react-leaflet

Hello, I am currently working on accessing information about my marker using React and react-leaflet, which is a library based on Leaflet. I initially tried to utilize useRef, but unfortunately it did not provide the expected results. When attempting to us ...

Issue with Material UI v5: Uncaught TypeError - Unable to access properties of an undefined object (specifically 'create')

After setting up the ThemeSetting.tsx context, I encountered an issue where I could not utilize <Button><Button> and other components that rely on the theme from Material UI React.js in TypeScript: error TypeError: Cannot read properties of u ...

Unable to make a POST request to the application

Creating a basic web page with server.js var path = require("path"); var express = require("express"); var app = express(); var bodyParser = require("body-parser"); app.listen(8000, function() { console.log("server is listening on port 8000"); }) app.us ...

Access a webpage in an html document using URL variables

In the process of developing an MVC web app without utilizing any MVC framework, I have created an index.html file with a section that dynamically loads all the views as needed by the user. However, I encountered an issue where direct URLs such as www.foo. ...

Chrome experiences a hidden stalling issue due to a large WebGL texture

While working with WebGL on Windows 10 using Three.js, I noticed that initializing a large (4096 x 4096) texture causes the main thread of Chrome to stall for a significant amount of time. Surprisingly, the profiler doesn't show any activity during th ...