Using TypeScript to assert the type of a single member in a union of tuples, while letting TypeScript infer the types of the other members

Currently, I am attempting to implement type assertion for the "error-first" pattern.

Within my function, it returns tuples in the format of ['error', null] or [null, 'non-error'].

The specific condition I want to check for is error === null, with TypeScript assuming that the second element is not-null.

To illustrate this further, consider the following example:

type ErrorMessage = string;
type ResultError = [ErrorMessage, null];
type ResultOk<T> = [null, T];
type Result<T> = ResultError | ResultOk<T>;

interface I {
    x: number;
}
function f(): Result<I> {
    if (Math.random() > 0.5) {
        return [null, {x: 1}];
    } else {
        return ['my-error', null];
    }
}

const [error1, result1]: Result<I> = f();

// Current approach, manually checking if result1 is not null...
if (result1 !== null) {
    console.log(result1.x);
}

// Desiring a different method where only error being null is checked,
// and result1 is automatically assumed as type I
if (error1 === null) {
    console.log(result1.x); // TS2531: Object is possibly 'null' <<=====
}

Answer №1

To help TS narrow down the type, we can utilize type guards. Take a look at the code snippet below:

const isOk = <T>(r: Result<T>): r is ResultOk<T> => r[0] === null; // defining a type guard

const result: Result<I> = f(); // result can be either Ok or Error

if (isOk(result)) {
  const [error1, result1] = result; // within this block, result is of type ResultOk
}

Additional information - while you could achieve similar functionality using a standard if statement like r[0] !== null, creating a named type guard for a custom structure is considered a more preferred approach.

Answer №2

Your code is perfectly aligned with the expected behavior.

Just a minor tweak: Avoid using destructuring to declare the results variable; instead, treat it as a single value:

const result: Result<I> = f();

if (result[0] === null) {
  const {x} = result[1]
  console.log(x); // works fine
}

This approach works as intended without requiring any additional functions or type guards.

A shoutout to this insightful answer that delves into why TypeScript behaves this way: Narrow type based on conditional type(Typescript)

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: ngx-responsive has a tendency to hide elements even if they meet the specified conditions

Recently, I started using a library called this to implement various designs for desktop and mobile versions of an Angular app (v4.2.4). Although the documentation recommends ngx-responsive, I opted for ng2-responsive but encountered issues. Even after set ...

Is AWS CDK generating nested cdk.out directories during synthesis?

Whilst working on my AWS CDK project for educational purposes, I found myself immersed in learning TypeScript, node.js, npm, and all related concepts simultaneously. Despite the mishap that occurred, requiring me to restart from the Github repository rathe ...

Interpolating strings in a graphQL query

Exploring the world of Gatsby and its graphQL query system for asset retrieval is a fascinating journey. I have successfully implemented a component called Image that fetches and displays images. However, I am facing a challenge in customizing the name of ...

Learn how to set up a class using TypeScript decorators

Is there a way to automatically initialize a class when a specific decorator is present above the class? For example: @apiController export class usersControllers extends lib.baseClasses.apiControllerBase().apiController { @lib.decorators.routesRegist ...

Guide to mocking the 'git-simple' branchLocal function using jest.mock

Utilizing the simple-git package, I have implemented the following function: import simpleGit from 'simple-git'; /** * The function returns the ticket Id if present in the branch name * @returns ticket Id */ export const getTicketIdFromBranch ...

Tips for overlaying a webpage with several Angular components using an element for disabling user interactions

I currently have an asp.net core Angular SPA that is structured with a header menu and footer components always visible while the middle section serves as the main "page" - comprised of another angular component. What I am looking to achieve is ...

Exploring Angular 2: How to Retrieve the Value of a Radio Button

How can I retrieve the value of the radio button that is clicked in app.component.html from within app.component.ts? app.component.html <div class="container"> <div class="row"> <div class="col-sm-3 well" style="width: 20%"> ...

Exploring the Concept of Extending Generic Constraints in TypeScript

I want to create a versatile function that can handle sub-types of a base class and return a promise that resolves to an instance of the specified class. The code snippet below demonstrates my objective: class foo {} class bar extends foo {} const someBar ...

Transferring Information Between Components

After logging into my login component, I want to pass data to my navbar component but unfortunately, my navbar content does not update. The navbar component is located in the app-module while the login component is in a separate module. I attempted to us ...

What is the best way to obtain the value of a nested formBuilder group?

Currently, my nested form is structured like this: this.form = this.formBuilder.group({ user: this.formBuilder.group({ id: ['', Validators.required], name: ['', Validators.required], phone: ['' ...

What is the proper way to enhance properties?

In the process of developing a Vue3 app using Typescript, one of the components is designed to receive data through props. Initially, everything functioned smoothly with the basic setup: props: { when: String, data: Object }, However, I de ...

What should be the output when ending the process using process.exit(1)?

I need to update my code by replacing throw new Error('Unknown command.') with a log statement and process.exit(1);. Here is the example code snippet: private getCommandByName = (name: string): ICommand => { try { // try to fetch ...

Incorporating an offset with the I18nPluralPipe

Having trouble with my multiselect dropdown and the text pluralization. I attempted to use the I18nPluralPipe, but can't seem to set an offset of 1. ListItem = [Lion, Tiger, Cat, Fox] Select 1 Item(Tiger) = "Tiger", Select 3 Item(Tiger, Cat, Fox) = ...

Angular Routing can be a powerful tool for managing multiple article posts in an efficient and organized way

I am in the process of building a website with Angular that features numerous articles. Whenever a user clicks on an article, I want it to navigate to a new URL using routing. To achieve this, I have created a new Article component and here is how my app- ...

React-table fails to show newly updated data

I am facing an issue with my react-table where real-time notifications received from an event-source are not being reflected in the table after data refresh. https://i.stack.imgur.com/q4vLL.png The first screenshot shows the initial data retrieval from th ...

Here is a way to return a 400 response in `express.js` when the JSON request body is invalid

How can I make my application send a response with status code 400 instead of throwing an error if the request body contains invalid JSON? import express from 'express' app.use(express.urlencoded({ extended: false })) app.use(express.json()) ...

Angular // binding innerHTML data

I'm having trouble setting up a dynamic table where one of the cells needs to contain a progress bar. I attempted using innerHTML for this, but it's not working as expected. Any suggestions on how to approach this? Here is a snippet from my dash ...

Is it feasible to use a component in a recursively manner?

Following a two-hour search for a solution, I decided to reach out to experts as I suspected the answer might be simpler than expected. The project in question is an Angular7 one. In my goals component, I aim to include a "goal" with a button labeled "+". ...

What is the process for testing an iframe and retrieving all of the response headers?

I'm currently working on a web application that can display URLs in an iframe. However, I also want to be able to test the URL before showing it in the iframe. The goal is to wait for a response and only display the iframe if there are no errors or if ...

Incorporating timed hover effects in React applications

Take a look at the codesandbox example I'm currently working on implementing a modal that appears after a delay when hovering over a specific div. However, I've encountered some challenges. For instance, if the timeout is set to 1000ms and you h ...