Skip to main content
Version: v8.0.0

Mapping

In Hyper-fetch, you can intercept and modify both outgoing requests and incoming responses. This powerful feature, known as mapping, allows you to transform data, add or modify headers, and even hydrate data into class models, all without affecting the core cached data. Mappers can be synchronous or asynchronous, giving you the flexibility to perform complex data manipulations, such as fetching additional data to enrich a response.

What you'll learn
  1. How to map responses to transform incoming data.
  2. How to use asynchronous mappers to enrich responses with additional data.
  3. How to hydrate response data into class models for better data handling.
  4. How to map requests to modify outgoing data, such as converting it to FormData.
  5. How to dynamically add headers to a request within a mapper.

Response Mapping

You can set the mapping of data returned from the request. This data will not be modified in the cache; it will only be modified within the given request. This means that the mapping will be included locally and not globally in the cache.

By making two requests to the same place with two different mappers, we can still use everything the library offers—deduplication, cache, etc. The mapper does not affect other requests, only the one to which it is attached. Thanks to this, we do not have to change the cache keys for each mapped request.

info

Mappers can be asynchronous. This way we can build up bigger responses.

Here's an example of using a response mapper to enrich the user data with a list of products and transform a date string into a Date object.

// Let's assume we have this request to fetch products
const getProducts = client.createRequest<ProductModel[]>()({
method: "GET",
endpoint: "/products",
});

export const getUser = client
.createRequest<{ response: UserModel }>()({
method: "GET",
endpoint: "/users/:userId",
})
.setResponseMapper(async (response) => {
if (response.data) {
// Fetch additional data
const productsResponse = await getProducts.send();
const products = productsResponse.data || [];

return {
...response,
data: {
...response.data,
products, // Add custom data to response
createdAt: new Date(response.data.createdAt), // Always transforms ISO string to Date!
},
};
}
return response;
});

Model Hydration

We can hydrate class models, which can parse our data. For example, the User class can parse dates from an ISO string into a Date object, generate nicknames or full names, etc. This is a powerful way to work with structured data.

class User {
id: number;
name: string;
createdAt: Date;

constructor(data: UserModel) {
this.id = data.id;
this.name = data.name;
this.createdAt = new Date(data.createdAt);
}

get profile() {
return `${this.name} (since ${this.createdAt.getFullYear()})`;
}
}

export const getUser = client
.createRequest<{ response: UserModel }>()({
method: "GET",
endpoint: "/users/:userId",
})
.setResponseMapper((response) => {
// Note that we are returning an instance of User, not the plain object.
// The type in createRequest should be `UserModel`, which is the raw data from the server.
// The use of the mapper and `User` class will provide the hydrated model to the application.
if (response.data) {
return { ...response, data: new User(response.data) };
}
return response;
});

Request Mapping

You can also map requests to make changes to a request before executing it. This allows us to validate the sent data and change its format or state, for example, to send it as FormData.

info

Request mappers can also be asynchronous, allowing for complex pre-request logic.

In this example, we convert the request payload into FormData and add a custom header before sending the request.

export const postUser = client
.createRequest<{ response: UserModel; payload: UserData }>()({
method: "POST",
endpoint: "/users/:userId",
})
.setRequestMapper((request) => {
const formData = new FormData();

Object.entries(request.data).forEach(([key, value]) => {
formData.append(key, value);
});

// Add custom headers and map data for every request
return request.setHeaders({ ...request.headers, "X-Better-Typed": "My custom header" }).setPayload(formData);
});

Congratulations!

You've mastered request and response mapping in Hyper-fetch!

  • You can transform incoming data using .setResponseMapper() without affecting the cache.
  • You can enrich responses by making additional requests within an asynchronous mapper.
  • You can hydrate data into class models for more robust data structures.
  • You can modify outgoing requests using .setRequestMapper() to format data or add headers.
  • You understand that mappers provide a local, per-request transformation, ensuring data consistency across your application.