⚡ Z-Fetch

💪 Using Hooks

Hooks allow you to hook into the request lifecycle.

Looking for interceptors?

You want to hook into some lifecycle, great, they're called hooks!

Hooks are a powerful feature that allow you to hook into the request lifecycle in order to intercept it and modify the request or response. Hooks can be used to implement features such as authentication, logging, custom caching, and more.

This is a common pattern in fetch libraries and so you can use it in kinda similar use cases.

Explanation

  1. Each hook is set when setting up your instance
  2. Each hook's value is a callback which has a context in it's argument.
  3. The context has the request, result and config of the instance at runtime.
  4. You can modify and return your modified config, request or result respectively.
  5. Basically, you return the modified context or a partial of it you have modified.
  6. The modification is what will be applied to the request or response accordingly.

The context object has the following properties:

A) request: The request object that is being sent.

This includes url, method and options for that request.

B) result: The result object that is being received.

This is Z-fetch result including data, error, response and other properties.

C) config: The configuration object that is being used.

-> Tip: you can always just pick only what you want to modify, and then return that after modifying it.

Request Hooks

Request hooks allow you to modify the request before it's sent:

const api = createInstance({
  baseUrl: 'https://jsonplaceholder.typicode.com',
  hooks: {
    onRequest: async ({ request }) => {
      console.log('Request about to be sent:', request);
 
      // Add a custom query parameter to all GET requests
      if (request.method === 'GET') {
        return {
          request: {
            ...request,
            url: request.url + '?custom=true',
          },
        };
      }
 
      // Add a timestamp to all requests
      return {
        request: {
          ...request,
          options: {
            ...request.options,
            headers: {
              ...request.options.headers,
              'X-Timestamp': Date.now().toString()
            }
          }
        }
      };
    }
  }
});

Response Hooks

Response hooks allow you to modify the response before it's returned:

const api = createInstance({
  baseUrl: 'https://jsonplaceholder.typicode.com',
  hooks: {
    onResponse: async ({ result }) => {
      console.log('Response received:', result);
 
      // Transform the response data
      if (result?.data) {
        return {
          result: {
            ...result,
            data: {
              ...result.data,
              processed: true,
              timestamp: Date.now()
            }
          }
        };
      }
 
      return { result };
    }
  }
});

Combining Request and Response Hooks

You can use both request and response hooks together:

const api = createInstance({
  baseUrl: 'https://jsonplaceholder.typicode.com',
  hooks: {
    onRequest: async ({ request }) => {
      // Add authentication header
      return {
        request: {
          ...request,
          options: {
            ...request.options,
            headers: {
              ...request.options.headers,
              'Authorization': 'Bearer your-token-here'
            }
          }
        }
      };
    },
    onResponse: async ({ result }) => {
      // Process successful responses
      if (result?.data && !result.error) {
        return {
          result: {
            ...result,
            data: {
              ...result.data,
              processedAt: new Date().toISOString()
            }
          }
        };
      }
 
      // Handle errors
      if (result?.error) {
        console.error('API Error:', result.error);
        // You could transform error formats here
      }
 
      return { result };
    }
  }
});

Real-World Example: Authentication and Logging

const api = createInstance({
  baseUrl: 'https://api.example.com',
  hooks: {
    onRequest: async ({ request }) => {
      // Log all outgoing requests
      console.log(`[${new Date().toISOString()}] Sending ${request.method} request to ${request.url}`);
 
      // Add authentication token from localStorage
      const token = localStorage.getItem('auth_token');
      if (token) {
        return {
          request: {
            ...request,
            options: {
              ...request.options,
              headers: {
                ...request.options.headers,
                'Authorization': `Bearer ${token}`
              }
            }
          }
        };
      }
 
      return { request };
    },
    onResponse: async ({ result, config }) => {
      // Handle 401 Unauthorized errors
      if (result?.error && result.error.status === 401) {
        // Clear invalid token
        localStorage.removeItem('auth_token');
 
        // Redirect to login page
        window.location.href = '/login';
      }
 
      // Add metadata to successful responses
      if (result?.data) {
        return {
          result: {
            ...result,
            data: {
              ...result.data,
              _meta: {
                timestamp: Date.now(),
                environment: process.env.NODE_ENV
              }
            }
          }
        };
      }
 
      return { result };
    }
  }
});

With these powerful hooks, you can implement complex request/response processing, authentication flows, logging, error handling, and data transformation in a clean and reusable way.

Any more hooks you think would be useful? Feel free to suggest them!

On this page