FD3
FD3

Reputation: 1956

How to add an optional type using Pick utility type?

I have a component for making HTTP requests, and one of the properties ('address') should be optional, since I don't need it in one of the components where I call that function.

The way I did it before is to use the Partial utility type, but I want to be more specific and make a use of Pick.

Old version:

export type Trip = {
 address: string
 product: string
 no: number
 customer: string
 delievered: boolean
 name: string
 surname: string
}


export const usePostTrip = () => {
  const queryClient = useQueryClient()
  const mutation = useMutation(
    async (data: Partial<Trip>) => {  // This is achieved with Partial
      const url = `${BASE_URL}/trip`
      return axios.post(url, data ).catch((e) => console.log(e.message))
    },
  )

  return mutation
}

Here is my current solution, which I believe is not the best one. Is there a better solution for this case?

export type Trip = {
 address: string
 product: string
 no: number
 customer: string
}

type tripWithoutAddress = Pick<
  Trip, 'product'| 'no'
>

type tripsWithAddress = Pick<
  Trip, 'product'| 'no' | 'address'
>

export const usePostTrip = () => {
  const queryClient = useQueryClient()

  const mutation = useMutation(
    async (data: tripWithoutAddress | tripsWithAddress) => {   // This is achieved with Pick
      const url = `${BASE_URL}/trip`
      return axios.post(url, data).catch((e) => console.log(e.message))
    },
  )

  return mutation
}

Upvotes: 0

Views: 4611

Answers (3)

n8o
n8o

Reputation: 1943

Create type util and use it:

type TOptionalFields<T, U extends keyof T> = Omit<T, U> & Partial<Pick<T, U>>;

TOptionalFields<Trip, "address">

Upvotes: 1

jsejcksn
jsejcksn

Reputation: 33748

I think Jimmy's answer is good, but:

If you ever change the address property on Trip (e.g. from string to number), you'll also have to manually change the optional version in order to maintain type consistency.

By using Pick and Partial, you can parametrically derive the type from Trip: any changes to the address property on Trip will automatically be changed in the derived type, too:

TS Playground

type Trip = {
 address: string;
 customer: string;
 no: number;
 product: string;
}

type TripWithOptionalAddress = Omit<Trip, 'address'> & Partial<Pick<Trip, 'address'>>;

declare const t: TripWithOptionalAddress;
t.address // string | undefined

Upvotes: 5

Jimmy
Jimmy

Reputation: 37081

It's not clearly stated in your question, but it sounds like you want a version of Trip where only the address property is optional, and the others remain required. This can be achieved by omitting the address property and then adding it back with an optional modifier by using an intersection type.

export type Trip = {
  address: string;
  product: string;
  no: number;
  customer: string;
};

export type TripWithOptionalAddress = Omit<Trip, "address"> & { address?: string; };

// All fields are required.
const trip: Trip = {
  address: "123 TypeScript Ave",
  product: "Stack Overflow",
  no: 123,
  customer: "Greg",
};

// `address` is missing.
const tripWithoutAddress: TripWithOptionalAddress = {
  product: "Stack Overflow",
  no: 123,
  customer: "Greg",
};

// `address` is still allowed, if present.
const tripWithAddress: TripWithOptionalAddress = trip;

Upvotes: 1

Related Questions