Using optional chaining with TypeScript types

I'm dealing with a complex data structure that is deeply nested, and I need to reference a type within it. The issue is that this type doesn't have its own unique name or definition. Here's an example:

MyQuery['system']['errors']['list'][number]

I generate the MyQuery type automatically from a graphql query using graphql-codegen. I'm trying to determine the type of a single error, but I face two challenges:

  1. All intermediate values are nullable.
  2. I lack a distinct identifier for the error in my auto-generated types.

I've attempted the following solutions:

  1. This method works, but it's difficult to read:
type Error = NonNullable<NonNullable<NonNullable<MyQuery>['system']>['errors']>['list'][number]
  1. This approach doesn't work (?.['field'] also fails)
type Error = MyQuery?['system']?['errors']?['list']?[number]
  1. This solution works, but introduces unnecessary variables:
const error = queryResult?.system?.errors?.list?.[0]
type Error: typeof error
  1. Although somewhat effective, this method results in non-null fields inside Error, which is not desired
import { DeepNonNullable } from 'utility-types'

type Error = DeepNonNullable<MyQuery>['system']['errors']['list'][number]

In essence, I am seeking a simpler way to implement "optional chaining for types" in TypeScript. Given that my API contains a lot of null values, it would be incredibly beneficial if there was a more straightforward approach than using multiple NonNullable<T> statements.

Answer №1

Is there a simpler way to implement "optional chaining for types"?

Unfortunately, at this time, there is no built-in method to "optionally chain" deeply nested types. However, it is possible to emulate this functionality using a complex recursive conditional generic type and paths. First, you would need a reusable helper function to handle index signatures:

type _IndexAccess<T, U extends keyof T, V extends string> = V extends "number" 
    ? Exclude<T[U], undefined> extends { [x:number]: any } ? 
        Exclude<T[U], undefined>[number]
        : undefined
    : V extends "string" ?
        Exclude<T[U], undefined> extends { [x:string]: any } ?
            Exclude<T[U], undefined>[string]
            : undefined
    : V extends "symbol" ?
        Exclude<T[U], undefined> extends { [x:symbol]: any } ?
            Exclude<T[U], undefined>[symbol]
            : undefined
    : undefined;

Next, you can create a helper type for recursively navigating the nested type using infer and template literal types to process the path:

type DeepAccess<T, K extends string> = K extends keyof T 
    ? T[K] 
    : K extends `${infer A}.${infer B}` 
        ? A extends keyof T 
            ? DeepAccess<Exclude<T[A], undefined>, B>
            : A extends `${infer C}[${infer D}]`
                ? DeepAccess<_IndexAccess<T, C extends keyof T ? C : never, D>, B>
                : undefined
    : K extends `${infer A}[${infer B}]` 
        ? A extends keyof T 
            ? B extends keyof T[A] 
                ? T[A][B] 
                : _IndexAccess<T, A, B>       
            : undefined
    : undefined;

While not the most elegant solution, this approach allows for seamless lensing into nested types:

type MyQuery = {
    system?: {
        errors?: {
            list?: [{
                answer: 42,
                questions: { known: false }[]
            }]
        }
    }
};

// false
type t1 = DeepAccess<MyQuery, "system.errors.list[number].questions[number].known">;

// [{ answer: 42; questions: { known: false; }[]; }] | undefined
type t2 = DeepAccess<MyQuery, "system.errors.list">;

// 42
type t3 = DeepAccess<MyQuery, "system.errors.list[number].answer">;

// undefined
type t4 = DeepAccess<MyQuery, "system.errors.list.unknown">;

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

Aligning two identical components within the same container when triggered by a single click

Currently, I am exploring Angular 2 and Typescript and have been developing a pager component for a table. The pager functions correctly, but I have encountered an issue with having two pagers - one above the table and one below it. When the next button on ...

utilize console.log within the <ErrorMessage> element

Typically, this is the way the <ErrorMessage> tag from Formik is utilized: <ErrorMessage name="email" render={(msg) => ( <Text style={styles.errorText}> ...

registering a back button action in Ionic2 for multiple pages

Currently, I am in the process of developing my Ionic2 app and have encountered a dilemma regarding the functionality of registerBackButtonAction. On one page, let's call it pageA, I have implemented this function and everything is functioning as exp ...

An error has occurred during the Next.js build process: ReferenceError - The component is not defined

Encountering an error during the yarn build process, but no issues with yarn dev My Typography component is custom-made, and I utilize absolute imports with the baseUrl option in tsconfig.json next version: v9.5.2, typescript version: 3.9.7 See error ou ...

Unable to loop through using ngFor

I have a component that retrieves data from the back-end and groups it accordingly. Below is the code snippet: getRecruitmentAgencyClientPositions(): void { this._recruitmentAgencyClientsService.getRecruitmentAgencyClientPositions(this.recruitmentAge ...

Accessing the currently operating WS server instance with NodeJS

After successfully setting up a basic REST API using NodeJS, ExpressJS, and routing-controllers, I also managed to configure a WebSocket server alongside the REST API by implementing WS. const app = express(); app.use(bodyParser.json({limit: "50mb"})); a ...

After being awaited recursively, the resolved promise does not perform any actions

When working with the Twitter API, I need to make recursive method calls to retrieve tweets since each request only returns a maximum of 100 tweets. The process is straightforward: Call the function and await it Make an HTTP request and await that If the ...

Avoid stopping Bootstrap Vue's events

Need help with a b-form-select control in Bootstrap Vue. Trying to execute a function when the value changes, but want the option to cancel the event and keep the original value. Complicating matters, the select is in a child component while the function ...

When working with Typescript, an error may occur related to index types even though the constant object and its

I am struggling with understanding TypeScript, specifically when it comes to a problem I encountered. Hopefully, someone can shed some light on this for me. My issue revolves around a functional component that is responsible for displaying the correct com ...

Tips for creating a deepCss selector for an input Textbox in Protractor

When I attempt to use sendKeys in an input textbox with Protractor, the element is located within a shadow-root and there are three separate input textboxes. ...

Access network path through browser using Angular

I'm having trouble opening a network path from my browser using the code below. The browser keeps throwing an error saying it's unable to load local resources. Can you please provide some advice on this issue? public openUrl() { window.o ...

What is the best way to fetch data before a component is rendered on the screen?

I am facing an issue with fetching data from a local server in Node. When I try to render the component, the array 'users' from the state appears to be empty, resulting in no users being displayed on the screen as intended. What's strange is ...

Exploring ways to simulate an event object in React/Typescript testing using Jest

I need to verify that the console.log function is triggered when the user hits the Enter key on an interactive HTMLElement. I've attempted to simulate an event object for the function below in Jest with Typescript, but it's not working as expecte ...

Exploring the Possibilities of Nipplejs Integration in Vue with Quasar

Trying to implement Nipplejs in my Vue Project using quasar Components. Installed nipplejs through npm install nipplejs --save. Attempted integration of the nipple with the code snippet below: <template> <div id="joystick_zone">&l ...

Angular 4's OrderBy Directive for Sorting Results

I've been working on implementing a sorting pipe based on the code provided in this resource: The issue I'm facing revolves around handling undefined values within my data. The sorting pipe functions correctly when there are no undefined values ...

Nextjs 14 experiences full page loading due to the presence of multiple root layouts

The issue I'm facing involves a full page load when navigating between two root layout pages In my Next.js application (NextJS 14), I have created two root layouts. However, when moving from the first layout to the second layout, it triggers a comple ...

Transferring dynamic parameters from a hook to setInterval()

I have a hook that tracks a slider. When the user clicks a button, the initial slider value is passed to my setInterval function to execute start() every second. I want the updated sliderValue to be passed as a parameter to update while setInterval() is r ...

Angular 13: SyntaxError Encountered: Token 'export' Not Recognized

After upgrading Angular from version 12 to 13, I encountered an error when running the app: "Uncaught SyntaxError: Unexpected token 'export'." Here are some additional details for context: In the angular.json configuration file, I had specified ...

How to bring in images from the assets folder using React and Typescript

I'm facing an issue where direct image importing is working, but when using object types, it's not functioning properly. Why could this be happening? I am currently working with "react": "^16.12.0" and "typescript": "~3.7.2" // ./src/assets/baby ...

Managing various situations using observables

Currently facing a coding dilemma: I have an observable that I need to subscribe to and certain requirements must be met: 1 - The registerUser function should only execute after processing the callback data. 2 - If registerTask returns data, I receive an ...