TypeScript Challenge #3: RTK Query named hooks based on endpoint definitions
Posted on September 24, 2025
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.
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:
You may try your skill with this widget before the explanations below:
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:
TypeScript in Mapped Types also allows us to modify the key using as
:
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:
Since we also need to capitalize the first letter, TypScript has a utility type Capitalize<K>
:
If we come back to Mapped Type again, we may apply this transformation to each object key after the keyword as
:
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:
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:
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:
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:
The solution is straightforward:
Combine everything together
I guess we already have everything that we need — let’s combine the two solutions together:
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.