Retrieve an array containing values whose types are derived from a rest parameter

I am seeking to develop a React hook for retrieving user settings. Here is a simplified example illustrating this:

import React from "react";

interface UserSettings
{
    SHOW_TIME_IN_HEADER: boolean;
    GRID_SIZE: number;
}

const USER_SETTINGS: UserSettings = {
    SHOW_TIME_IN_HEADER: true,
    GRID_SIZE: 8,
};

export const useUserSetting = <S extends keyof UserSettings>(...setting: S[]): UserSettings[S][] =>
{
    return setting.map(s => USER_SETTINGS[s]);
};

export const Component = () =>
{
    const [
        showTime, // Should be boolean but it is number | boolean
        gridSize // Should be number but it is number | boolean
    ] = useUserSetting("SHOW_TIME_IN_HEADER", "GRID_SIZE");

    return (
        <div>
            {"..."}
        </div>
    );
};

TS Playground

I aim for the hook to provide an array containing values of its respective type. In this scenario, both returned values share the type number | boolean, whereas one should exclusively be of type number or boolean.

Is such differentiation achievable? And what would be the appropriate term for it? My primary hurdle at present lies in articulating this issue accurately for research purposes.

If you possess the solution to my dilemma or can articulate the problem more concisely, please do not hesitate to reach out. Thank you.

Answer №1

If you aim to define the return data type of useUserSetting as a mapped tuple type, it's crucial not to categorize the setting rest parameter as S[]. This is because simply using an array type won't retain the sequence. Instead, make the generic type parameter S represent an array type itself and assign the type S to the setting parameter. By doing so, the compiler can successfully infer a tuple type for S.

This would be my suggested typing:

export const useUserSetting = <S extends Array<keyof UserSettings>>(
    ...setting: S): {
        [I in keyof S]: UserSettings[Extract<S[I], keyof UserSettings>]
    } => {
    return setting.map(s => USER_SETTINGS[s]) as any; // requires an assertion here
};

The return type iterates through the numeric keys I of the S tuple, generating UserSettings[S[I]]. Unfortunately, the compiler cannot verify whether S[I] will be a key within UserSettings. To address this, I utilize the Extract utility type. The notation

Extract<S[I], keyof UserSettings>
validates that only elements of S[I] which are assignable to keyof UserSettings are retained, essentially enabling us to proceed without issues.

It should also be noted that the implementation,

setting.map(s => USER_SETTNGS[s]
, fails to adhere to the mapped tuple type. The typings from Array.prototype.map() solely produce an Array, losing any ordering information. In the presented approach, a type assertion to any has been utilized. Presumably, the focus lies more on enhancing the experience for the callers of useUserSetting() rather than the developer themselves.

const [
    showTime, // boolean
    gridSize // number
] = useUserSetting("SHOW_TIME_IN_HEADER", "GRID_SIZE");

gridSize.toFixed(); // functions correctly

Great news! The return type of useUserSetting() results in [number, boolean], making gridSize identifiable as a number, allowing for appropriate usage (e.g., gridSize.toFixed() doesn't trigger a compiler error).

Playground link to code

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

Preventing Circular Dependencies in RollupJS and TypeScript: Ensuring Build Failure When Circular Dependencies are Detected

I recently created a React component library using RollupJS (2.7) and TypeScript (3.9.10), and I encountered an issue with circular dependencies being reported: >> yarn build (!) Circular dependency src/index.ts -> src/components/index.ts -> sr ...

When running `npm test`, Mocha TS tests encounter failure, but the issue does not arise when executing them

When running tests in my Typescript nodejs project, I use the following command: mocha --compilers ts:ts-node/register,tsx:ts-node/register The tests run successfully with this command. However, when I try to run them using npm test, I encounter the foll ...

Adding client-side scripts to a web page in a Node.js environment

Currently, I am embarking on a project involving ts, node, and express. My primary query is whether there exists a method to incorporate typescript files into HTML/ejs that can be executed on the client side (allowing access to document e.t.c., similar to ...

Exploring Dependency Injection in Angular2: A Comparison of TypeScript Syntax and @Inject Approach

I'm currently working with Angular2 build 2.0.0-alpha.34 and I can't figure out why I'm getting different results from these two code snippets. The only variation is between using @Inject(TitleService) titleService and titleService: TitleSe ...

The module 'ngx-cookie-service' could not be located

I am currently working with Angular 12 and recently I installed a cookie service using the following command: npm install --save ngx-cookie-service Within my app.module.ts file, when I try to import the 'CookieService' like so: import { Cookie ...

TypeScript type identifiers and their misuse as values: "only meant for a specific type, but being utilized as a value in this context."

Struggling with implementing type guards in Typescript 3.2.2: interface Bird { fly(): boolean; } interface Fish { swim(): boolean; } function checkIfSwim(x: Fish | Bird) { if ((<&Fish>x).swim) { return true } } Encounterin ...

Is it possible to define a unique function signature in a child class when implementing a method from a parent class?

In the process of developing a repository module that includes generic methods, I have found that as long as each derived class has the `tableName` configured, the basic query for creating, finding, or deleting records remains consistent across all child c ...

Error message: `Socket.io-client - Invalid TypeError: Expected a function for socket_io_client_1.default`

I have successfully installed socket.io-client in my Angular 5.2 application, but after trying to connect (which has worked flawlessly in the past), I am encountering a strange error. TypeError: socket_io_client_1.default is not a function at new Auth ...

Excessive geolocation position responses in Angular 5

I am trying to implement an Angular 5 component that will continuously fetch my current location every 3 seconds if it has changed. Here is a snippet of my code: export class WorkComponent implements OnInit { constructor(private userService: UserService ...

Tips for serializing the execution of an array of observables

I am currently working on a validation process that goes through data in a table row by row. Due to the fact that each row validation requires access to a shared resource, it is crucial that the access to this resource is serialized. public validate():Obse ...

Safe way to implement map and spread operator in your codebase

Is there a workaround for this issue? I am working with an interface, IFoo, and an array of data IFoo[]. My goal is to map this data and modify a single property. It should look something like this const mapper = (foos: IFoo[]): IFoo[] => { return foo ...

Revealing a typescript function for interacting with HTML pages through manual calls

Currently, I am in the process of developing a TypeScript modular library that will be bundled using Parcel JS. The main purpose of this library is to provide specific functionality to applications. In order for the consuming application or web page to uti ...

Utilizing Angular: Invoking a function from a directive within a component

I'm attempting to invoke a method from a directive. Let's assume I have a directive myDirective.ts @Directive({ selector: '[something]', }) export class myDirective implements OnInit { .... public myMethod(){ console.log("it works") } ...

What is the best way to incorporate TypeScript into a simple JavaScript project and release it as a JavaScript library with JSDoc for TypeScript users?

Issue: I have encountered difficulties finding an efficient solution for this. Here's a summary of what I attempted: To start, ensure that allowJs and checkJs are both set to true in the tsconfig.json, then include the project files accordingly. Ty ...

What are the most optimal configurations for tsconfig.json in conjunction with node.js modules?

Presently, I have 2 files located in "./src": index.ts and setConfig.ts. Both of these files import 'fs' and 'path' as follows: const fs = require('fs'); const path = require('path'); ...and this is causing TypeScr ...

Tips for enabling users to import from subdirectories within my NPM package

Is there a way to allow users to import from subfolders of my TypeScript NPM package? For instance, if the TypeScript code is structured like this: - lib - src - server - react Users should be able to import from the subfolders as package-name/react, ...

Tips for deleting a certain element from an array with the aid of a service in Angular 9

Is there a way to remove an object named Test1 from an array of objects? I am struggling with finding the correct method to achieve this. If there are any errors in my code, please feel free to point them out. I have utilized a service to update the values ...

Can the child component ensure that the Context value is not null?

In the process of developing a Next.js/React application with TypeScript, I've implemented a UserContext in pages/_app.js that supplies a user: User | null object to all child components. Additionally, there's a ProtectedRoute component designed ...

Building basic objects in TypeScript

My current project involves utilizing an interface for vehicles. export interface Vehicle { IdNumber: number; isNew: boolean; contact: { firstName: string; lastName: string; cellPhoneNumber: number; ...

What could be causing my if statement to fail even though the condition is met?

I'm attempting to generate dynamic fields based on my chosen attributes. I have two array objects called addAttributes and fakeAttributes. The fakeAttributes contain the details of the selected attributes. I have a dropdown select component that displ ...