Creating a conditional partial property in Typescript without using type assertion

I am currently working on creating a type that includes an optional property based on the generic parameter passed. Here is an example of what I have in mind:

type PartialWhen<Bool, T> = Bool extends true ? Partial<T> : T;
type PartialWhenExtends<Base, Parent, T> = PartialWhen<Base extends Parent ? true : false, T>

type Props<V> = {
  value: V
} & PartialWhenExtends<V, string, {
  getValue: (item: V) => string
}>

function getPropsValue<V>({ value, getValue }: Props<V>): string {
  if (getValue !== undefined) {
    return getValue(value);
  }

  return value;
}

const props1: Props<string> = {
  value: '123'
}
console.log(getPropsValue(props1))

const props2: Props<number> = {
  value: 123,
  getValue: (val) => val.toString()
}
console.log(getPropsValue(props2))

const props3: Props<string> = {
  value: 'abc',
  getValue: (val) => val.concat(val)
}
console.log(getPropsValue(props3))

The issue arises when attempting to return the value in the getPropsValue function, where TypeScript shows an error stating:

Type 'V' is not assignable to type 'string'
. Although the required condition for getValue is checked before this point.

To address this problem, I tried asserting the value as string, but also experimented with different versions of partial types definitions and union types without success. Another approach involved checking if the value is of type string and returning an empty string if it's not, though this solution doesn't work well with generics.

As I plan to utilize the PartialWhenExtends frequently, I would like to find a method to avoid assertions throughout the code. Are there any adjustments I can make to the partial types definition so that TypeScript can accurately infer the types? If not, do you have any other suggestions that could be applicable in this scenario?

Answer №1

Here is a solution to the problem:

  1. Correct your code for the Props<string | number> scenario
  2. Ensure that your code explicitly checks for a string type
type PartialWhen<Bool, T> = Bool extends true ? Partial<T> : T
type PartialWhenExtends<Base, Parent, T> = Base extends Parent ? Partial<T> : T

type Props<V> =
    { value: V } &
    PartialWhenExtends<[V], [string], { getValue: (item: V) => string }>
    
function getPropsValue<V>({ value, getValue }: Props<V>): string {
    if (getValue !== undefined) {
        return getValue(value)
    }
    if (typeof value === 'string')
        return value
    throw new Error()
}
// Correctly functioning examples
getPropsValue<string>({ value: 'asd' })
getPropsValue<'asd'>({ value: 'asd' })
getPropsValue<string & { a: 1 }>({ value: 'asd' as any })

// Examples with errors
getPropsValue<string | number>({ value: 123 })
getPropsValue<number>({ value: 123 })

You can call your function like this:

getPropsValue<string | number>({} as any)
function getPropsValue<string | number>({ value, getValue }: {
    value: string | number;
} & ({
    getValue: (item: string | number) => string;
} | Partial<{
    getValue: (item: string | number) => string;
}>)): string

Therefore, TypeScript is correct in pointing out issues in your code.

I have managed to make the code work without assertion by adjusting it as follows:

function getPropsValue<V>(
    data: V extends string
        ? { value: V, getValue?: undefined } | { value: V, getValue: (item: V) => string }
        : { value: V, getValue: (item: V) => string }
): string
getPropsValue<number | string>({} as any)

However, there is an issue when it doesn't account for non-split

{ value: V, getValue?: undefined }
, causing it to stop working correctly.

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

Tips for managing open and closed components within a React accordion and ensuring only the clicked component is opened

Unique Accordion component: const CustomAccordion = (props: AccordionProps) => { const { label, levels, activeId, id } = props const [isExpand, setIsExpand] = useState(false) const onPress = useEvent(() => { setIsExpand( ...

What is the best way to filter and sort a nested tree Array in javascript?

Looking to filter and sort a nested tree object for a menu If the status for sorting and filtering is true, how do I proceed? const items = [{ name: "a1", id: 1, sort: 1, status: true, children: [{ name: "a2", id: 2, ...

Using NodeJS API gateway to transfer image files to S3 storage

I have been attempting to upload an image file to S3 through API Gateway. The process involves a POST method where the body accepts the image file using form-data. I crafted the lambda function in TypeScript utilizing the lambda-multipart-parser. While it ...

Tips for incorporating Extract<T, U> with a nested variant?

I've encountered an issue with generated types. A particular API is providing me with two types, and I want to create distinct aliases for each. In TypeScript, we can utilize Extract<> to assist with this: type Add = { type: 'add' ...

tsconfig.json: No input files were detected in the configuration file

I am encountering an issue with my tsconfig.ts file where it says "No inputs were found in config file 'd:/self-study/Web/Learning/Express/tsconfig.json'. Specified 'include' paths were '["**/*"]' and 'exclude&a ...

Problem with (click) event not triggering in innerHtml content in Angular 4

For some reason, my function isn't triggered when I click the <a... tag. Inside my component, I have the following code: public htmlstr: string; public idUser:number; this.idUser = 1; this.htmlstr = `<a (click)="delete(idUser)">${idUser}&l ...

Make sure to execute observables just once, and afterwards verify if they have finished - angular2 typescript

Completely new to the world of Observables, so please forgive me if this is a simple question. I've done some research online, but I can't seem to find the answer I'm looking for. Take a look at this basic example on Plunker: https://plnk ...

Error encountered when using Redis data type in Typescript

Hello, I'm currently attempting to implement Redis on typescript but keep encountering this error with my code. I have installed "redis": "^4.0.4", "@types/redis": "^4.0.11". How can I resolve this issue? const idUser: string Argument of type '[s ...

Incorporating and modifying a component's aesthetics using styled-components: A comprehensive guide

My OverviewItem component has 2 props and is a styled-component. I want to change just one style on this component, which can be done by using the technique of styling any component. Does creating a wrapper component remain the only option for sharing st ...

When utilizing useRef and useCallback in React, the output is visible in the console log but does not appear on the page

When working with API data, it's important to remember that the extraction process is asynchronous and the state may not be available at certain times. To handle this situation, we can utilize useCallback. However, even after successfully logging the ...

Navigating the missing "length" property when dealing with partial functions generated using lodash's partialRight

I've been utilizing MomentTimezone for time manipulation within the browser. My development stack includes TypeScript and Lodash. In my application, there is an accountTimezone variable set on the window object which stores the user's preferred ...

Issue with binding nested ViewModels/components in Knockoutjs using TypeScript does not resolve

Struggling with implementing a viewModel within another viewModel in knockout. Any assistance would be greatly appreciated. Using typescript and aiming to have a list of address controls, each with their individual viewmodel. Initially, the project functi ...

Combine the date and time into one variable

Is there a way to save the date and time fields in a variable with the format: 0000-00-00T00:00:00.000Z? component.html <mat-form-field appearance="outline" class="pr-sm-8" fxFlex="50"> <mat-label>Fecha Inicio ...

TypeScript throws an error when attempting to call a user-defined event handling function

I have created a custom event handling function like this: /** Trigger an event when clicking outside of a specific node */ export function eventHandlers(node: HTMLElement) { const handleClick = (event: MouseEvent) => { if (node && ...

Unable to associate with 'paint' as it is not a recognized attribute of 'mgl-layer' while using mapbox in Angular 9

I am currently working on an Angular 9 project with the latest version of mapbox integrated. My goal is to toggle between displaying contours and museums on the map. To achieve this, I have installed the package: "@types/mapbox-gl": "^1.12. ...

Receive the most recent information from Angular's service method

I offer a unique service. handler.ts import { Observable,of,Subject } from 'rxjs'; import { PlayerService } from "./../../core/services/player.service"; import { Injectable } from "@angular/core"; import { DeezerService } from "../services/deez ...

Determining the specific condition that failed in a series of condition checks within a TypeScript script

I am currently trying to determine which specific condition has failed in a set of multiple conditions. If one does fail, I want to identify it. What would be the best solution for achieving this? Here is the code snippet that I am using: const multiCondi ...

What is the reason for receiving an error with one loop style while the other does not encounter any issues?

Introduction: Utilizing TypeScript and node-pg (Postgres for Node), I am populating an array of promises and then executing them all using Promise.all(). While pushing queries into an array during iteration over a set of numbers, an error occurs when the ...

Encountering issue in Angular 14: Unable to assign type 'Date | undefined' to type 'string | number | Date' parameter

I have been working on an Angular 14 project where I am implementing a Search Filter Pipe. Below is the code snippet I am using: import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'transferFilter' }) export class Trans ...

Utilizing Ramda lenses for composition in Typescript with the useState set function in React

I am currently learning functional programming and exploring event handling in React. Let's consider the following example: interface Todo { task: string done: boolean } interface TodoProps { todo: Todo onChange: ChangeEventHandler< ...