Logo

TypeScript Challenge #3: RTK Query named hooks based on endpoint definitions

CN
vladtopolev

Posted on September 24, 2025

TypeScript
TypeScript Challenge
Lookup Types
Mapped Types
Template literal

When I first started using RTK Query and explored its type system, I had a real “wow” moment. What impressed me most was how it generates strongly-typed React hooks directly from the endpoint definitions.

Loading...

Let’s split this task into several mini challenges and combine all of them at the end to provide a solution

Challenge 1

Implement a TypeScript utility type NamedHooks<T> that takes an object-like type T and creates a new type that:
- Renames every key by adding prefix it with use and postfix Query
- Capitalizes the original key’s first letter.
- Preserves each property’s value type exactly (no changes to nullability, readonly, optional, or the type itself).

📌 Example:

Loading...

You may try your skill with this widget before the explanations below:

Named React Hooks
Test Cases:0 of 3 passed
NOT RUN

Task:

NamedHooks<{ getPokemon: string }>

Expected result:

{ useGetPokemonQuery: string }

NOT RUN

Task:

NamedHooks<{ getPokemons: number; getHeroes: string}>

Expected result:

{ useGetPokemonQuery: number; useGetHeroesQuery: string }

NOT RUN

Task:

NamedHooks<{}>

Expected result:

{}

Your Code
Loading...

At first, we should use the concept of Mapped Types in TypeScript, which allows us to create a new object-like type iterating by its keys:

Loading...

TypeScript in Mapped Types also allows us to modify the key using as:

Loading...

And in this case, using the String Template Literal feature of TypeScript gives us a way to modify the key by adding a prefix use with syntax that should be familiar from JavaScript:

Loading...

Since we also need to capitalize the first letter, TypScript has a utility type Capitalize<K>:

Loading...

If we come back to Mapped Type again, we may apply this transformation to each object key after the keyword as:

Loading...

But there’s a catch: keys in object-like types can be string, number, or symbol. However, template literal types only work with string and number keys — not with symbol. It is not difficult to fix and transform only the key with a type string, and ignore any other key if they might be provided in the target type. We do it with a ternary condition:

Loading...

You may try this solution with a widget above and ensure it works as expected.

Challenge 2

If you look at the RTK query API, you will see two types of hooks: query and mutation, with the following transformation rules:

Query:
getPokemon -> useGetPokemonQuery

Mutation:
updatePokemon -> useUpdatePokemonMutation

How could we distinguish whether it is a query or a mutation?

The secret here is that when we build endpoints, it creates an endpoint definition for each of them, and the shape looks like that:

Loading...

I intentionally skipped a lot of information, which helps us focus on the important part only. 

Here, we are going to use a pattern known as “tagged union” or “discriminated union”. A tagged (discriminated) union is a union of object types that all share a common literal “tag” property. That tag lets TypeScript narrow the union automatically when you check it, making each variant’s fields safe to use. 

Have a look at the type EndpointDefinition again — it actually aligns with the definition that I provided before; therefore, based on this “tag,” we may define whether it’s “query” or “mutation”.

Let’s complete the simple challenge.

Let’s assume that we have the following endpoint declaration:

Loading...

The challenge is to implement two util types:
-IsQuery<T> — evaluates to true if the given endpoint definition is a query, otherwise false.
- IsMutation<T> — evaluates to true if the given endpoint definition is a mutation, otherwise false.

For example:

Loading...


Tagged Union Pattern
Test Cases:0 of 4 passed
NOT RUN

Task:

IsQuery<typeof endpoints.getPokemon>

Expected result:

true

NOT RUN

Task:

IsQuery<typeof endpoints.updatePokemon>

Expected result:

false

NOT RUN

Task:

IsMutation<typeof endpoints.getPokemon>

Expected result:

false

NOT RUN

Task:

IsMutation<typeof endpoints.updatePokemon>

Expected result:

true

Your Code
Loading...

The solution is straightforward:

Loading...


Combine everything together

I guess we already have everything that we need — let’s combine the two solutions together:

Loading...

I’d really encourage you to take this challenge from scratch. Don’t just read the solution — actually get your hands dirty and work it out yourself. That’s the fastest (and most fun) way to build real TypeScript muscle.

RTK Query named hooks based on endpoint definitions
Test Cases:0 of 1 passed
NOT RUN

Task:

keyof NamedEndpointHooks<typeof endpoints>

Expected result:

'useGetPokemonQuery' | 'useUpdatePokemonMutation'

Your Code
Loading...