Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript optional object key not behaving as expected

I have an object with conditional keys. I.e.:

const headers: RequestHeaders = {};

if (...) {
  headers.foo = 'foo';
}

if (...) {
  headers.bar = 'bar';
}

I'm new to TS and I expected this to work:

type RequestHeaders = {
  foo?: string,
  bar?: string,
};

However, I'm passing this to fetch and the type definition for fetch's headers is { [key: string]: string }. I'm getting:

Type 'RequestHeaders' is not assignable to type '{ [key: string]: string; }'.
  Property 'foo' is incompatible with index signature.
    Type 'string | undefined' is not assignable to type 'string'.
      Type 'undefined' is not assignable to type 'string'.

The only way I could get this to work is type RequestHeaders = { [key: string]: string };. Is there a way to limit the keys to a set of predefined strings?

like image 264
Leo Jiang Avatar asked Feb 24 '20 22:02

Leo Jiang


2 Answers

The fetch API does not accept a headers object that has a key with an undefined value. Since each of your optional types can be either string | undefined, the compiler is rejecting them.

Here is an approach that filters the headers to remove those with undefined values. Its type predicate (is) keeps the compiler happy.

const buildHeaders = (requestHeaders: RequestHeaders): HeadersInit =>
  Object.entries(requestHeaders).filter(
    (entry): entry is [string, string] => entry[1] !== undefined
  );

const headers: RequestHeaders = {};

type RequestHeaders = {
  foo?: string; // optional
  bar?: string; // optional
  baz: string; // required!
};

fetch("Some Data", {
  headers: buildHeaders(headers)
});

The advantage of this approach is that it lets you limit the keys to a set of predefined strings while also letting you to specify whether each is required or optional.

like image 115
Shaun Luttin Avatar answered Oct 17 '22 12:10

Shaun Luttin


The header data of fetch method must be type like

type HeadersInit = Headers | string[][] | Record<string, string>;

For your case, I think you will define headers type as a alias of Record<string, string>. For keys configuration (foo, bar), I will have a suggestion: Fixed header keys. Then, you will define all header keys to a type like:

type HeaderKeys = 'foo' | 'bar';

type RequestHeaders = Record<HeaderKeys, string>; // the same: type RequestHeaders = Record<'foo' | 'bar', string>;

const headers: RequestHeaders = {} as RequestHeaders; // force cast
like image 1
hoangdv Avatar answered Oct 17 '22 12:10

hoangdv