Typescript encounters a failure in typing when an object is destructured

There is a function that returns an object with two properties (res, mes) where one of them could be null:

const fetchJSON = <Res, Body>(link: string, body: Body): Promise<{ res: Res; mes: null } | { res: null; mes: Popup }> => {
  return new Promise(resolve => {
    fetch(link,
      body: JSON.stringify(body)
    })
      .then(res => res.json())
      .then((resJson: Res) => {
        resolve({ res: resJson, mes: null })
      })
      .catch(err => {
        resolve({ res: null, mes: { type: 'err', text: 'some error' } })
      })
  })
}

Using the response of fetch without destructuring works perfectly fine:

 const result = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { id })
 if (result.mes) return popupPush(result.mes)
 setProfile(result.res.reader)

However, when using object destructuring:

const { res, mes } = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { readerId, role: 'alfa' })
  if (mes) return popupPush(mes)

  console.log(res.id)

Typescript doesn't recognize that res is not null even after checking mes:

https://i.sstatic.net/gAu5S.png

Is there a way to fix this issue or should I avoid object destruction?

Alternatively, is there another approach for handling wrappers like this?

Answer №1

If you are confident that the property you're working with will never be null, you can employ the non-null assertion operator (!) to declare that it is not null or undefined.

For example, console.log(res!.id):

function getResult(type: 'res' | 'mes') {
  let result: {
    res?: { foo: string },
    mes?: { bar: string },
  } = {};

  if (type === 'res') {
    result.res = {
      foo: 'res',
    };
  } else {
    result.mes = {
      bar: 'mes',
    };
  }

  return result;
}

const { res, mes } = getResult('res');

console.log(res!.foo); // non-null assertion
console.log(mes?.bar); // optional chaining

Furthermore, you have the option to utilize the optional chaining operator (?) which allows the expression to return null if the preceding property is null.

Answer №2

It is my understanding that the current limitation of TypeScript involves deconstructing types. The type

{ res: Res; mes: null } | { res: null; mes: Popup }
presents a scenario where both res and messages can be null during unpacking.

Once the variables have been separated, the union previously formed between them cannot be linked back together. Currently, overcoming this limitation requires implementing guards within a single object.

If your data structures allow for it, one potential solution is to return a single differentiable field:

const fetchJSON = <Res, Body>(link: string, body: Body): Promise<{ res: Res; mes: null } | { res: null; mes: Popup }> => {
  return new Promise(resolve => {
    fetch(link,
      body: JSON.stringify(body)
    })
      .then(res => res.json())
      .then((resJson: Res) => {
        resolve(resJson)
      })
      .catch(err => {
        resolve({ type: 'err', text: 'some error' })
      })
  })
}

function isErrorMessage(obj: any): obj is Popup {
    return obj && obj.type === "err";
}

async function MAIN(){

    const result = await fetchJSON<ResReaderData, ApiReaderData>('api/reader_data', { id })
     if (isErrorMessage(result)) return popupPush(result)
     setProfile(result.reader)
}

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

Webpack and React.js: Additional loaders might be required to manage the output generated by these loaders

An error occurred while parsing the module in ./productFlow/index.tsx at line 3, column 12. The file was processed with the following loaders: * ./node_modules/awesome-typescript-loader/dist/entry.js. It seems like an additional loader may be needed to h ...

How can I simulate a callback function that was not tested?

Currently experimenting with the method below: startScriptLoad(): void { const documentDefaultView = this.getDocumentDefaultView(); if (documentDefaultView) { const twitterData: ICourseContentElementEmbedTweetWidgetData = this.getTwitterWid ...

Changes are reflected in the service variable, but they are not updating in the component

I am facing an issue with a variable that is supposed to track the progress of image uploads. The variable seems to be working fine, but it remains undefined in my component. Upload Service method uploadProfilePic(url:string, user_id:string, image:any) { ...

Utilizing a dictionary for comparing with an API response in order to generate an array of unique objects by eliminating duplicates

I currently have a React component that utilizes a dictionary to compare against an API response for address state. The goal is to map only the states that are returned back as options in a dropdown. Below is the mapping function used to create an array o ...

Utilizing emotion with MUI v5 for dynamic theming

After upgrading MUI from v4 to v5, I'm facing some difficulties grasping the concept of theming with the various solutions available. I find it challenging to determine when to use MUI theming/styling components and when to opt for emotion ones. Whil ...

Header Express does not contain any cookies, which may vary based on the specific path

In my express.js app, I have two controllers set up for handling requests: /auth and /posts. I've implemented token authorization to set the Authorization cookie, but I'm encountering an issue when making a request to /posts. The request goes th ...

Working with Array of Objects Within Another Array in Angular 6 Using Typescript

I'm currently working with an array of "food": "food": [ { "id": 11, "name": "Kabeljaufilet", "preis": 3.55, "art": "mit Fisch" }, { "id": 12, "name": "Spaghetti Bolognese", "preis": 3.85, "art": "mit Fleisch" }, { "id": 1 ...

What steps do administrators (coaches) need to take in order to generate a new user (athlete) using Firebase Cloud Functions?

I am currently developing a web application designed for coaches and athletes. The main functionality I am working on is allowing coaches to add athletes to the platform. Once added, these athletes should receive an email containing a login link, and their ...

Tips for utilizing method overload in TypeScript with a basic object?

Looking at this code snippet: type Foo = { func(a: string): void; func(b: number, a: string): void; } const f: Foo = { func(b, a) { // ??? } } An error is encountered stating: Type '(b: number, a: string) => void' is not assign ...

Error in Mongoose Schema Configuration Detected in NestJS App

I'm currently developing an e-commerce application using NestJS and MongoDB with Mongoose. I've been facing an issue while trying to implement a user's shopping cart in the application. The error message I keep encountering is as follows: ...

What is the correct way to import scss files in a Next.js project?

Currently, I am working on a NextJS project that uses Sass with TypeScript. Everything is running smoothly in the development environment, but as soon as I attempt to create a build version of the project, I encounter this error. https://i.stack.imgur.com ...

What is the proper way to send a list as a parameter in a restangular post request

Check out this code snippet I found: assignUserToProject(pid: number, selectedUsers: any, uid: number) { let instance = this; return instance.Restangular.all("configure/assign").one(pid.toString()).one(uid.toString()).post(selectedUsers); } ...

Assign a value to a file input in Angular 6

I'm currently working with Angular 6 and I have a requirement to retrieve an image that is dropped into a div element and assign it as the value of an input type="file" within a form. The process involves the user dropping an image into the designate ...

Analyze two sets of JSON data and compile a new JSON containing only the shared values

I am trying to generate two JSON arrays based on a shared property value from comparing two sets of JSON data. this.linkedParticipants =[ { "id": 3, "name": "Participant 2", "email": "<a href="/ ...

Proper utilization of react-hook-form in conjunction with TypeScript and material-ui to display error messages efficiently

Currently, I am using a combination of react-hook-form with typescript and material-ui. My goal is to pass the error message as a helperText to the TextField. I attempted to achieve this by utilizing helperText={errors.email?.message} however, TypeScript ...

Enhancing Vue functionality with vue-class-component and Mixins

In my Vue project, I am using vue-class-component along with TypeScript. Within the project, I have a component and a Mixin set up as follows: // MyComp.vue import Component, { mixins } from 'vue-class-component' import MyMixin from './mixi ...

Extract data from an HTTP request and assign it to variables using filter and map in Angular

I need to extract data from an http get request and assign it to 3 separate variables once fetched. Data: [ { reportId: 1, results1: [{ name: "TotalReferralsMade", optionId: 3082, factor: 1, description: null ...

Outputting double columns with Typescript Sequelize associations

I am currently in the process of constructing table models using sequelize along with typescript. Here is an example of one of my models: 'use strict'; import { Model, InferAttributes, InferCreationAttributes, CreationOptional } from 'seque ...

Utilizing the MapToIterable Angular Pipe with TypeScript

Exploring the implementation of a pipe in Angular. Discovered that ngFor doesn't work with maps, prompting further research to find a solution. It seems that future features may address this issue, but for now, utilizing a mapToIterable pipe is the re ...

Converting HTML to an array using Angular

Is there a way to convert HTML into an array of entities? For example: 'hi <em>there</em>' => ['hi', '<em>', 'there', '</em>'] ...