What is the best way to selectively retrieve a combination of keys from multiple objects within an array?

Consider the following scenario:

const common = {
  "test": "Test",
  "test2": "Test2"
}

const greetings = {
  "hello": "Hello"
}

export const locales = (["en", "nl"] as const);
export type I18nMap = Record<typeof locales[number], {
    common: typeof common;
    greetings: typeof greetings;
}>;;

I am looking to be able to choose a single key like "common" or an array of keys such as

["common", ""greetings"
. Depending on the selected key(s), I want to have the ability to select any of the keys within the chosen object. I'm unsure how to accomplish this, below is my attempt

type Namespace = keyof I18nMap[keyof I18nMap];

export function useTranslation<
  T extends Namespace | Namespace[],
  U extends T extends Namespace
    ? keyof I18nMap[keyof I18nMap][Namespace]
    : keyof I18nMap[keyof I18nMap][Namespace][number]
>(namespace: T) {}

For example, when the namespace is "common", I anticipate only "test" | "test2" to be available for type U. If I select an array ["common", "greetings"], I expect a union of "test" | "test2" | "hello" for type U

Answer №1

To start off, let's transfer the

{common: typeof common; greetings: typeof greetings}
type to an interface for easier manipulation:

interface I18nData {
  common: typeof common;
  greetings: typeof greetings;
}

export type I18nMap = Record<typeof locales[number], I18nData>;

type Namespace = keyof I18nData;

The type T should only extend Namespace (and not Namespace | Namespace[]), while the parameter namespace should be T | T[]. By specifying a mixed array in namespace, TypeScript will infer T as the combination of those types.

Next, U needs to extend keyof I18nData[T]. Yet, simply implementing this results in an error:

export function useTranslation<
  T extends Namespace,
  U extends keyof I18nData[T]
>(namespace: T | T[], key: U) {}
// Error: Argument of type 'string' is not assignable to parameter of type 'never'.
useTranslation(['common', 'greetings'], 'test');

This issue arises because T comprises of 'common' | 'greetings' and I18nData[T] transforms into

{test1: string; test2: string} | {hello: string}
. Since there are no shared keys among the union types, keyof I18ndata[T] becomes never.

To rectify this, you can employ distributive conditional types:

export function useTranslation<
  T extends Namespace,
  // Obtain keyof I18nData[T] for each T
  U extends T extends unknown ? keyof I18nData[T] : never
>(namespace: T | T[], key: U) {}

// Successful cases
useTranslation('common', 'test');
useTranslation('greetings', 'hello');
useTranslation(['common', 'greetings'], 'test');
useTranslation(['common', 'greetings'], 'hello');

// Unsuccessful cases
useTranslation('common', 'hello');
useTranslation('greetings', 'test');
useTranslation(['common'], 'hello');

Playground link

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

Combining two observables into one and returning it may cause Angular guards to malfunction

There are two important services in my Angular 11 project. One is the admin service, which checks if a user is an admin, and the other is a service responsible for fetching CVs to determine if a user has already created one. The main goal is to restrict ac ...

Is there a more concise method for accepting a collection of interfaces in TypeScript?

Issue I am facing a simplified version of a problem with my model: Here is how my model currently looks: interface Instrument { name: string; // ...more properties shared by all instruments... } interface Guitar extends Instrument { type: &q ...

Creating an Observable with no data in Angular 6 using Rxjs 6

Currently, I am diving into the world of Angular 6 with RxJS 6 and have stumbled upon a question regarding the scenario where I need to return a null or empty Observable in case the response fails or encounters an exception. In this situation, my assumptio ...

Transferring Information Across Angular Components via a Service Utilizing an RxJS Subject

The Goal I'm Pursuing In my Angular client app, I have two separate components. One component handles data fetching and processing from the server. My objective is to utilize the subscribe callback function to navigate to a different page where I can ...

File manager built with React, allowing users to easily remove files stored in the browser or on a local host

I am in the process of developing a reactjs based web application for file management on my local machine. Currently, I am utilizing code from a project located at https://github.com/knyzorg/simple-file-explorer which employs WebSockets to display files. M ...

What are the steps to resolve the issue of assigning void type to type ((event: MouseEvent<HTMLDivElement, MouseEvent>) => void) | undefined in a react application?

I'm trying to update the state isOpen to true or false when clicking on a div element, but I keep getting an error with the following code: function Parent() { const [isOpen, setIsOpen] = React.useState(false); return ( <Wrapper> ...

Restricting Dates in Angular 2 Date Picker

I encountered an issue while attempting to disable specific dates in a date picker. Here is my custom date picker written in TypeScript: import { DateFormatter } from './ng2-bootstrap/date-formatter'; import { DatePickerComponent } from './n ...

Angular Material 2/4/5: Retrieving the unselected value from a multi-select dropdown

Hello everyone, I am currently utilizing Angular Material for a multi-select dropdown feature. While I have successfully retrieved the selected values, I am having difficulty obtaining the unchecked values for the dropdown. Can someone offer assistance w ...

Guide on utilizing a declaration within a third-party module

I have encountered an issue while using the fingerprintjs2 library, as the declaration provided in DefinitelyTyped seems incomplete and incompatible. In order to resolve this, I decided to create my own declaration within my project. However, I am facing ...

How can NgRx be used to properly reset or empty an array within the state object?

What is the proper way to reset an array in an NgRx reducer? I am using NgRx to create a basic reducer that includes an empty array called myArray in the initial state: import * as MyActions from './my.actions'; const myState = { myValue: & ...

What is the best way to implement custom sorting for API response data in a mat-table?

I have been experimenting with implementing custom sorting in a mat-table using API response data. Unfortunately, I have not been able to achieve the desired result. Take a look at my Stackblitz Demo https://i.sstatic.net/UzK3p.png I attempted to implem ...

Changing the selection on multiple input fields in Angular

I am working with two select elements in my form. Based on the selected value from the first select, I am filtering data for the second select. Additionally, I have an option to add the same row of form if desired. My issue is that when I select a value fr ...

What could be the reason for the variable's type being undefined in typescript?

After declaring the data type of a variable in TypeScript and checking its type, it may show as undefined if not initialized. For example: var a:number; console.log(a); However, if you initialize the variable with some data, then the type will be display ...

The assignment of type 'null' to type 'number' is not valid

In my user.model.ts file, I have a UserID with the type 'number'. In my user.component.ts file, I have a function that resets my form. resetForm(form?: NgForm) { if(form!=null) form.resetForm(); this.service.formData = { UserID ...

Is it best to make a refactored/service method public or private in Typescript?

Objective: I need to refactor a method in my component that is used across multiple components, and move it to a service class for better organization. Context: The method in question takes a user's date of birth input from a profile form and convert ...

Jasmine test confirms that momentJS objects with the same values do not match

In my angular-cli project, I have a set of Jasmine tests that include various assertions. One particular assertion looks like this: expect(dispatchSpy).toHaveBeenCalledWith({ type: 'SET_RANGE', payload: { value: 'Weekly', start: mome ...

Encountering an Issue with Vue 3 and Vue Router 4: Uncaught TypeError - Trying to Access Undefined Properties (specifically 'push')

I'm currently working with Vue 3, Vue Router 4, and TypeScript in my project. However, I've encountered an issue while trying to utilize router.push(). Every time I attempt this, I receive a console error stating: Uncaught (in promise) TypeError: ...

Generate a list of items in typescript, and then import them into a react component dynamically

I have a variable that stores key/value pairs of names and components in a TypeScript file. // icons.tsx import { BirdIcon, CatIcon } from 'components/Icons'; interface IconMap { [key: string]: string | undefined; } export const Icons: IconM ...

Restricting the types within a generic union type in TypeScript

//Custom declaration: interface TickListFilter { type: "tickList"; value: string; } interface ColorFilter { type: "color" value: ColorValueType } type FilterType = TickListFilter | ColorFilter; ... updateValue = (filte ...

Utilizing IonPage and DeepLinkMetadataType in Ionic 3 for tab navigation: Eliminating the need for tab-0

One of the pages on my site contains tabs that are only loaded when clicked: explore.html <ion-tabs> <ion-tab [root]="tab1Root" [tabTitle]="hotTitle" tabIcon="flame"></ion-tab> <ion-tab [root]="tab2Root" [tabTitle]="searchTitle ...