Steps for necessitating an object property in either of two function arguments

One interesting scenario involves a factory that returns a method. This factory has the capability to set a default id, eliminating the need for it to be passed to the method.

Below is a basic test case (Playground) for demonstration:

type FactoryOptions = {
  id?: number
}

type MethodOptions = {
  id?: number
}

function factory (options: FactoryOptions) {
  return method.bind(null, options)
}

function method (state: FactoryOptions, options: MethodOptions) {
  const id = state.id || options.id
  console.log(id.toString())
}

The use of id.toString() in this context serves as a trigger for TypeScript to raise a warning about the potential undefined nature of id. This question stems from a more complex issue discussed in octokit/auth-app.js#5.

Answer №1

This issue has two facets... the perspective of the caller in method(), and how method() is implemented. In TypeScript, it's usually more straightforward to ensure smooth behavior for callers compared to handling it within implementations.

On the caller's end, we can enforce that method must be invoked with at least one argument containing a defined id property. One approach is through using overloads. The example shown below demonstrates this concept:

type WithId = {
  id: number;
};

function method(state: FactoryOptions & WithId, options: MethodOptions): void;
function method(state: FactoryOptions, options: MethodOptions & WithId): void;
function method(state: FactoryOptions, options: MethodOptions) {
   // implementation
}

By utilizing overloads, callers can invoke either call signature while the actual implementation is abstracted from them:

method({}, {}) // results in an error
method({id: 1}, {}) // valid
method({}, {id: 1}) // valid

However, dealing with this scenario within the implementation context is more challenging. There isn't a simple way to convince the compiler that a value of type

[number, number | undefined] | [number | undefined, number]
has a defined value at either index. Unfortunately, treating such a union as a discriminated union doesn't yield the desired result. While a custom type guard could potentially solve this, it might be excessive.

Instead, employing a type assertion allows us to work around this limitation:

function method(state: FactoryOptions & WithId, options: MethodOptions): void;
function method(state: FactoryOptions, options: MethodOptions & WithId): void;
function method(state: FactoryOptions, options: MethodOptions) {
  const id = (typeof state.id !== "undefined"
    ? state.id
    : options.id) as number; // assert as number
  console.log(id.toString()); // works correctly now
}

Furthermore, I opted to replace your check on state.id with a ternary operation to handle cases where 0 may cause issues due to its falsy nature. This ensures that id maintains its numeric nature as intended. Though this may not align completely with your expectations, it serves as the most viable solution given the circumstances. Best of luck!

Link to code

Answer №2

In addition to the insightful response provided by jcalz, here is an alternative approach (check out this playground) for your review.

type FactoryOptions = {
  id?: number
}

type MethodOptions = {
  id?: number
}

function factory (options: FactoryOptions) {
  return method.bind(null, options)
}

const hasId = (input: any): input is {id: number} => 
  typeof input.id !== 'undefined'; 

function method (state: Omit<FactoryOptions, 'id'>, options: Required<MethodOptions>): void;
function method (state: Required<FactoryOptions>, options: Omit<MethodOptions, 'id'>): void;
function method (state: FactoryOptions, options: MethodOptions): void {
  if(hasId(state)) {
    console.log(state.id.toString());
  } else if (hasId(options)) {
    console.log(options.id.toString());
  }
}

method({}, {}); // error
method({ id: 10 }, {}); // ok
method({ }, { id: 10 }); // ok

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

Angular 2 is struggling to locate the file with the name 'Set'

Currently, I am in the process of upgrading my project from angular2 beta15 to rc4. However, when I try to compile, I encounter the following error: path/node_modules/@angular/common/src/directives/ng_class.d.ts(81,35): error TS2304: Cannot find name &a ...

Ensure that a TypeScript string union literal array type does not contain any duplicate values

In my code related to defining SQL tables/views and their columns, I am utilizing literal string union types and arrays. Take a look at the sample code below featuring a mock user SQL table with columns: id, username, email, password.... export type UserT ...

Angular 7's URL updates, but no other actions take place

I am new to posting here and feeling quite desperate. Currently, I am working with Angular 7 and facing an issue. The problem arises when I manually enter the desired URL, everything works perfectly. However, when I use RouterLink by clicking a button, the ...

Exploring Angular 2's EventEmitter for Event Handling and Debugging

My attempt at creating a basic event emitter doesn't seem to be functioning properly. Here's the code snippet: Main Component This is the main app component I have been working on: @Component({ selector:'my-app', templateUrl: ...

Guide on sending a value to index.html from a component in Angular 2

In my angular2 project, the index.html file contains a header bar. The responsibility of logging in and displaying other content is handled by different components. I need to display a logo in the header bar only when a user is logged in. To achieve this, ...

Angular 2 forms, popping the chosen item in the `<select>` element

Check out the FormBuilder: let valuesArray = fb.array([ fb.group({ name: 'one' }), fb.group({ name: 'two' }), fb.group({ name: 'three' }), fb.group({ name: 'four' }) ]); this.for ...

Observable - initiating conditional emission

How can I make sure that values are emitted conditionally from an observable? Specifically, in my scenario, subscribers of the .asObservable() function should only receive a value after the CurrentUser has been initialized. export class CurrentUser { ...

Develop a new flow by generating string literals as types from existing types

I'm running into a situation where my code snippet works perfectly in TS, but encounters errors in flow. Is there a workaround to make it function correctly in flow as well? type field = 'me' | 'you' type fieldWithKeyword = `${fiel ...

Utilizing the value of a parent type in a different type in TypeScript: A guide

Currently, I am in the process of experimenting with creating my own basic ORM system. Below is the snippet of code that I have been working on: type Models = { User: { name: string }, Template: { text: string } } type ExtractKeysF ...

How can I capture the logs from Sentry and send them to my own custom backend system?

I successfully implemented Sentry in my Angular Application. Is there a method to retrieve logs from Sentry and transfer them to a custom endpoint? I aim to integrate the Sentry Dashboard with my backend (developed using Java Springboot). Appreciate the ...

Review all control documents

I initialize a formGroup like so this.availableSigners = new FormGroup({ nameSigner: new FormControl(), mailSigner: new FormControl() }); and in my HTML component I have the following structure <div *ngFor="let signer of signers; le ...

Utilizing custom hooks for passing props in React Typescript

I have created a unique useToggler custom hook, and I am attempting to pass toggle props from it to the button child in the Header component. However, when I try using toggle={toggle}, I encounter this error: Type '{toggle: () => void;}' is ...

What is the proper way to inform TypeScript that the method in my subclass will be returning a distinct type?

I currently have a class structure that resembles the following: class BasicType { coerce(value) { return String(value); } read(value) { return this.coerce(value); } } The coerce method consistently returns a string, while ...

Angular dependency issue: Expected '{' or ';' for @types/node

I encountered an error while running "ng serve" in my Angular application. Originally built as Angular 2, it was upgraded to Angular 8 (with attempts at versions 6 and 7 along the way). However, after migrating from Angular 5, I started experiencing errors ...

How can I utilize a lookup type when dealing with a member of a static class?

I have a Typescript class where I defined a class member for a specific type in the following way: export class AccordionSection extends Component { } export class Accordion extends Component<AccordionProperties> { public static Section = Acco ...

How can one specify a data type for JSON data in PostgreSQL?

I currently have a TypeScript interface that includes one property as a float and the other as a string. Although I am able to insert JSON data directly into a table, I am curious if there is a way to specify data types within the JSON data itself. inter ...

Tips for linking a composite element while utilizing React's forward ref (attribute is not present on forwardRefExoticComponent)

In my Panel component, I have implemented the option for users to utilize compound components. The default header of the panel includes a close button, but I designed it this way to give users the flexibility to create their own custom headers with differe ...

Is subtyping causing issues in TypeScript's inheritance model?

I am currently utilizing TypeScript for my coding projects, and I have observed that it can allow the production of non-type-safe code. Despite implementing all the "strict" options available to me, the behavior I am experiencing goes against the principle ...

Protractor typescript guide: Clicking an element with _ngcontent and a span containing buttontext

I'm struggling with creating a protractor TypeScript code to click a button with _ngcontent and span buttontext. Does anyone have any ideas on how to achieve this? The code snippet on the site is: <span _ngcontent-c6 class="braeting net-subheadi ...

Retrieve highlighted text along with its corresponding tag in ReactJS

my <span class="highlight">highlighted</span> word The text above is showing an example including HTML tags. However, when using window.getSelection(), only the text "my highlighted word" is returned without the surrounding <span& ...