Does Typescript allow for nesting Pick<> types in any way?

I've been exploring ways to enhance safety in my client-side GraphQL queries, and I'm open to suggestions for a better approach.

Currently, my query is structured like this:

export const tenantManagePageQuery = async (tenantId: string) =>
    graphQLClient.request<{
        tenants: TenantManagePageQueryTenant[];
    }>(
        /* GraphQL */ `
            query tenants($tenantId: String!) {
                tenants(tenantIds: [$tenantId]) {
                    id
                    description
                    name
                    approvedUsers {
                        id
                        alias
                    }
                    pendingUsers {
                        id
                        alias
                    }
                }
            }
        `,
        { tenantId },
    );

To define the TenantManagePageQueryTenant type, I usually do something similar to this:

interface TenantManagePageQueryTenant
    extends Pick<Tenant, 'id' | 'description' | 'name'> {}

The base Tenant model represents my GQL model type.

I'm wondering if there is a way to extend the `Pick` statement to also include nested properties.

For instance:

interface TenantManagePageQueryTenant
    extends Pick<Tenant, 'id' | 'description' | 'name' | Pick<approvedUser| 'id' | 'alias'> {}

Answer №1

In a similar vein to Colin's response, approaching the problem from a different angle can yield interesting results. If you find yourself needing to deconstruct an existing interface or type, utilizing indexes can be quite useful:


// Original type
type Tenant = {
    id:string;
    description:string;
    name:string;
    approvedUsers: Array<{
      id:string;
      alias:string;
    }>
}

// Deconstruction
type TenantManagePageQueryTenant = 
  Pick<Tenant, 'id' | 'description' | 'name'> & {
    approvedUsers: Array<Pick<Tenant['approvedUsers'][0], 'id' | 'alias'>>
  }

Option 1 - Simplified types

As mentioned in the comments, this can be slightly refined for better clarity:

type TenantSubset = Pick<Tenant, 'id' | 'description' | 'name'>
type ApprovedUserSubset = Pick<Tenant['approvedUsers'][number], 'id' | 'alias'>

type TenantManagePageQueryTenant = TenantSubset & { approvedUsers: Array<ApprovedUserSubset> }

Option 2 - Direct approach

Although more repetitive, this version may be easier to follow:

type TenantManagePageQueryTenant = { 
  id: Tenant['id'],
  description: Tenant['description'],
  name: Tenant['name'],
  approvedUsers: Array<{
    id: Tenant['approvedUsers'][number]['id'],
    alias: Tenant['approvedUsers'][number]['alias'],
  }> 
}

Interactive Playground

Answer №2

Although @Avius's code is a good start, there seems to be an error with the interface extending the intersection type. To resolve this, you should use the following type:

type TenantManagePageQueryTenant = Pick<Tenant, 'id' | 'description' | 'name'>
    & { approvedUsers: Pick<ApprovedUser, 'id' | 'alias'>[] }
{ }

interface Tenant {
    id:string;
    description:string;
    name:string;
}

interface ApprovedUser {
    id:string;
    alias:string;
}

let tenant:TenantManagePageQueryTenant = {
  id: "123",
  description: "456",
  name: "789",
  approvedUsers: [{
    id: "aaa",
    alias: "bbb" // To see the desired type warning, try removing 'alias' 
  }]
}

Playground Link

Answer №3

Perhaps utilizing intersection types could address the question, if I interpreted it correctly.

If approvedUsers is an array, the type definition could resemble this:

interface TenantManagePageQueryTenant extends
    Pick<Tenant, 'id' | 'description' | 'name'>
    & { approvedUsers: Pick<ApprovedUser | 'id' | 'alias'>[] }
{}

An example of a valid object of type TenantManagePageQueryTenant would be:

{
  id: "123",
  description: "456",
  name: "789",
  approvedUsers: [{
    id: "aaa",
    alias: "bbb"
  }]
}

However, the following examples would not be valid:

// missing alias in approvedUsers[0]

{
  id: "123",
  description: "456",
  name: "789",
  approvedUsers: [{
    id: "aaa"
  }]
}
// unknown field extra in approvedUsers[0]
{
  id: "123",
  description: "456",
  name: "789",
  approvedUsers: [{
    id: "aaa",
    alias: "bbb",
    extra: 345678
  }]
}

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

Issues with type errors in authentication wrapper for getServerSideProps

While working on implementing an auth wrapper for getServerSideProps in Next.js, I encountered some type errors within the hook and on the pages that require it. Below is the code for the wrapper along with the TypeScript error messages. It's importan ...

MSBUILD encounters numerous JQuery errors when compiling a web project with TypeScript

Currently, I am working on a .net core 3.1 (netcoreapp3.1) razor pages project that includes typescript files and a few javascript files. The project builds perfectly from Visual Studio 2019 (professional) as well as from the command line using MSBuild. H ...

What is the solution to the error message stating that the property 'pipe' is not found on the OperatorFunction type?

I implemented the code based on RxJS 6 documentation while working with angular 5, RxJS 6 and angularfire2 rc.10. However, I encountered the following error: [ts] property 'pipe' does not exist on type 'OperatorFunction<{}, [{}, user, str ...

What is preventing me from running UNIT Tests in VSCode when I have both 2 windows and 2 different projects open simultaneously?

I have taken on a new project that involves working with existing unit tests. While I recently completed a course on Angular testing, I am still struggling to make the tests run smoothly. To aid in my task, I created a project filled with basic examples f ...

The modal dialog from angular/material is unable to function properly when a shape on Google Maps is clicked

I have incorporated Google Maps into my application in order to showcase shapes (polygons/circles) and markers. To interact with Google Maps in Angular, I am utilizing the type definition "npm install --save @types/googlemaps". Upon clicking a shape, I nee ...

Using Selenium WebDriver with JavaScript: Handling Mouse Events (mouseover, click, keyup) in Selenium WebDriver

I am currently working on integrating Selenium testing for mouse events with dynamically generated elements. Specifically, I am attempting to trigger a "mouseover" event on an element and then interact with some icons within it. However, I have encountere ...

Receiving an error in Typescript when passing an object dynamically to a React component

Encountering a typescript error while attempting to pass dynamic values to a React component: Error message: Property 'title' does not exist on type 'string'.ts(2339) import { useTranslation } from "react-i18next"; import ...

Animation of lava effect in Angular 7

I have a unique Angular 7 application featuring a homepage with a prominent colored block at the top, along with a header and various images. My goal is to incorporate lava effect animations into the background similar to this CodePen example. If the link ...

How to display a nested array in TypeScript following a specific structure

Can anyone assist with printing the variable below in the specified format? Data export const shop_items: Inventory:{ Toys{ Balls: { BallId: 001uy, BallName: Soccerball, SignedBy: David_Beckham, }, ...

Solving the issue where the argument type is not assignable to the parameter type

I am attempting to filter an array of objects in React using TypeScript and encountered this error. Below is my interface, state, and function: TS2345: Argument of type '(prev: IBudget, current: IBudget) => IBudget | undefined' is not assigna ...

What could be causing the peculiar behavior I am experiencing when a child component attempts to display values from an object fetched in the parent component?

I am currently developing an Angular application and encountering a challenge when it comes to passing data from a parent component to a child component using the @Input decorator The parent component is named PatientDetailsComponent. Here is the TypeScri ...

The specified attribute 'title' is not found within the 'IntrinsicAttributes' type

When I work with React and TS, I encounter an issue - one of the properties in my array of dummy values always shows up as red. Here is my code: ProjectList.tsx const DUMMY_PROJECTS = [ { id: "p1", title: "My bug busines ...

The 'api' property is not found within the 'MyService' type declaration

Currently, I am working on a tutorial where we are building an Angular service to send emails from a form using the Mailthis Email API. In the code for the service, I encountered an error related to the 'api' variable that states "Property ' ...

Utilizing Functions in Next.js with TypeScript: A Guide to Reusability

It is considered a best practice to separate data fetching functions into a folder named services, but I'm having trouble implementing this in my Next.js project. The function works when placed inside the component where I want to render the data, but ...

Encountering dependency issues with minified directives that remain unresolved

My directive is functioning correctly when the application is not minified However, when I minify it, an error occurs Unknown provider: tProvider <- t <- dateTimeFilterDirective Is there a way to ensure the directive works even when minified? mod ...

Can you please tell me the name of the ??= operator in Typescript?

The Lit Element repository contains a function called range that utilizes the ??= operator. This operator resembles the nullish coalescing operator but with an equal sign. Do you know what this specific operator is called? Below is the complete code snipp ...

"Maximizing the Potential of refetchQueries in reason-apollo: A Comprehensive Guide

Encountering issues setting up refetchQueries with reason-apollo. Following a setup similar to the swapi example here, I have a listing and a form for adding new items via a mutation. Upon successful mutation, the goal is to refetch the items in the listin ...

Can a reducer be molded in ngrx without utilizing the createReducer function?

While analyzing an existing codebase, I came across a reducer function called reviewReducer that was created without using the syntax of the createReducer function. The reviewReducer function in the code snippet below behaves like a typical reducer - it t ...

What is the best way to format a string into a specific pattern within an Angular application

In my Angular component, I have 4 fields: customerName, startDate, and startTime. Additionally, I have a fourth field that is a textarea where the user can see the message that will be sent via email or SMS. Within my component, I have defined a string as ...

The path alias feature in Sveltekit seems to be malfunctioning

I've been working with Svelte Kit (using TypeScript) and I'm having trouble getting the new link "$base" to work for my shortlinks. I've added the shortlink information below: ./jsconfig.json { "compilerOptions": { "mod ...