Create a 'key remover' function which can process any generic object

Generics are really interesting in that they're tied to a function's execution context. What that means is that you can make a function, which has one generic locked inside of it, then make another function, in the future, out of that function, with the first generic still locked, and then add another one.

Here's what I mean by that. We want to make a function called makeKeyRemover. Now, makeKeyRemover, is going to return a function that removes keys from an object.

export const makeKeyRemover = () => () => {};

const keyRemover = makeKeyRemover(["a", "b"]);

const newObject = keyRemover({a: 1, b: 2, c: 3});

The expected case for newObject is that it's going to be an object with just the key of c. In keyRemover, we're making a function. In newObject, we're actually calling the function and using it.

To make this work, first of all, what we're going to do is say, <Key extends string> in makeKeyRemover.

export const makeKeyRemover = <Key extends string>() => () => {};

And then we're going to say that the argument keys is an array of keys. The key's generic has been locked in as a or b.

export const makeKeyRemover = <Key extends string>(keys: Key[]) => () => {};

What we want to do next is take in the object, which is going to be the generic Obj. Then, I'll set the argument obj to Obj.

export const makeKeyRemover = 
  <Key extends string>(keys: Key[]) => 
  <Obj>(obj: Obj) => {};

The generic is now locked in the keyRemover. That means that we need to just remove the keys from the object.

To do that we'll set the return type of this function to be Omit<Obj, Key>. Then we'll return that blank object and set the type to any to quite down an error.

export const makeKeyRemover = 
  <Key extends string>(keys: Key[]) => 
  <Obj>(obj: Obj): Omit<Obj, Key> => {
    return {} as any;
  };

What's going on here is that now we've got Omit, which removes keys from an object.

We're removing a and b, the keyRemover locks in that first generic, Key. Then newObject, when we instantiate the second function, locks in the generic, Obj. Now, newObject only has c available to it.

Pretty cool!

Transcript

0:00 Generics are really interesting in that they're tied to a function's execution context. What that means is that you can make a function, which has one generic locked inside of it, then make another function in the future out of that function with the first generic still locked and add another one.

0:16 Here's what I mean by that. Here, we want to make a function called Make Key Remover. Now, Make Key Remover, what it's going to return is a function to remove keys from an object. Here, the expected case for this object is that it's going to be a new object with just the key of C. Here, we're making a function. Here, we're actually calling the function and using it.

0:38 How do we get around to that? First of all, what we're going to do is we're going to say, "Key, extend string here." This is the first function, the key remover one. We're going to say, This key is a...or key array." This is an array of keys. You can see now that the key's generic has been locked in as A or B.

1:01 What we want to do next is we want to take in the object, which is going to be...I'm going to give it a generic of obj. Obj is going to be here. I'll just save it to get the formatting. Obj, as you can see here, it's been locked in. The generic is locked in here into the key remover. That means that we need to just remove the keys from the object.

1:23 We're going to return from this function. I think it's Omit, and then obj, and then key. If I just return this, I need to quieten down the error. What's going on here is that now we've got Omit, which removes keys from an object.

1:41 We're removing A and B. You can see that the key remover locks in that first generic, which is just up here. Then this one, when we actually instantiate the second function, is, it locks in this generic. The new object, it only has C available to it. Pretty cool.

Generics can be 'locked in' by function calls, meaning that generics can be 'curried' through functions. Here, we create a 'key remover' function which can process any generic object.

Discuss on Twitter

More Tips

Play Type Predicates

Type Predicates

1 min

Play TypeScript 5.1 Beta is OUT!

TypeScript 5.1 Beta is OUT!

2 mins

Play How to Name your Types

How to Name your Types

4 mins

Play Don't use return types, unless...

Don't use return types, unless...

4 mins

Play TypeScript 5.0 Beta Deep Dive

TypeScript 5.0 Beta Deep Dive

6 mins

Play Conform a Derived Type Without Losing Its Literal Values

Conform a Derived Type Without Losing Its Literal Values

1 min

Play Avoid unexpected behavior of React’s useState

Avoid unexpected behavior of React’s useState

1 min

Play Understand assignability in TypeScript

Understand assignability in TypeScript

2 mins

Play Compare function overloads and generics

Compare function overloads and generics

1 min

Play Use infer in combination with string literals to manipulate keys of objects

Use infer in combination with string literals to manipulate keys of objects

1 min

Play Access deeper parts of objects and arrays

Access deeper parts of objects and arrays

1 min

Play Ensure that all call sites must be given value

Ensure that all call sites must be given value

1 min

Play Understand how TypeScript infers literal types

Understand how TypeScript infers literal types

1 min

Play Get a TypeScript package ready for release to NPM in under 2 minutes

Get a TypeScript package ready for release to NPM in under 2 minutes

1 min

Play Use assertion functions inside classes

Use assertion functions inside classes

1 min

Play Assign local variables to default generic slots to dry up your code and improve performance

Assign local variables to default generic slots to dry up your code and improve performance

2 mins

Play Know when to use generics

Know when to use generics

2 mins

Play Map over a union type

Map over a union type

1 min

Play Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

1 min

Play Use generics to dynamically specify the number, and type, of arguments to functions

Use generics to dynamically specify the number, and type, of arguments to functions

1 min

Play Use 'declare global' to allow types to cross module boundaries

Use 'declare global' to allow types to cross module boundaries

2 mins

Play Turn a module into a type

Turn a module into a type

2 mins

Play Create autocomplete helper which allows for arbitrary values

Create autocomplete helper which allows for arbitrary values

2 mins

Play Use deep partials to help with mocking an entity

Use deep partials to help with mocking an entity

1 min

Play Throw detailed error messages for type checks

Throw detailed error messages for type checks

1 min

Play Use generics in React to make dynamic and flexible components

Use generics in React to make dynamic and flexible components

1 min

Play Create your own 'objectKeys' function using generics and the 'keyof' operator

Create your own 'objectKeys' function using generics and the 'keyof' operator

1 min

Play Write your own 'PropsFrom' helper to extract props from any React component

Write your own 'PropsFrom' helper to extract props from any React component

1 min

Play Use 'extends' keyword to narrow the value of a generic

Use 'extends' keyword to narrow the value of a generic

1 min

Play Use function overloads and generics to type a compose function

Use function overloads and generics to type a compose function

2 mins

Play Decode URL search params at the type level with ts-toolbelt

Decode URL search params at the type level with ts-toolbelt

2 mins

Play Use 'in' operator to transform a union to another union

Use 'in' operator to transform a union to another union

2 mins

Play Derive a union type from an object

Derive a union type from an object

2 mins