Request Deduplication

Optimize your API calls by preventing duplicate requests

Request deduplication prevents redundant API calls when multiple requests are made to the same endpoint. For example, if a user rapidly clicks a "Refresh Profile" button - without deduplication, it would trigger multiple API calls for the same data. With deduplication, these duplicate calls are handled based on your strategy.

Key benefits:

  • Prevents duplicate API calls
  • Reduces network requests
  • Prevents race conditions

How it works

Request deduplication works by:

  1. Generating a unique key for each request based on URL and parameters by default (can be customized via dedupeKey)
  2. Tracking in-flight requests using this key
  3. Handling requests with the same key (duplicates) according to your chosen strategy

Requests are only deduplicated when made from the same callApi instance. Requests made from different instances will be handled independently.

api.ts
import {  } from "@zayne-labs/callapi";

const  = ();

const  = ();
const  = ();

Shared deduplication - these requests are from the same callApi instance, so they will be deduped according to the strategy
const = ("/users"); const = ("/users"); // Will dedupe with resultOne
Independent deduplication - these requests are from different callApi instances, therefore no deduplication occurs between them
const = ("/users"); // Independent deduplication const = ("/users"); // Independent deduplication

Usage

CallApi provides three deduplication strategies:

1. Cancel Strategy (Default)

  • Cancels any existing request when a new identical request is made
  • Best for scenarios where you only need the latest data
api.ts
const { data } = await callMainApi("/users/123", {
	dedupeStrategy: "cancel",
});

Cancel Strategy Visualization

2. Defer Strategy

The defer strategy shares the same response promise between duplicate requests. If a request is made while an identical one is still in-flight, it will receive the same response.

Defer Strategy Visualization

Example: Multiple components requesting user data simultaneously

api.ts
import { useState, useEffect } from "react";

function ProfileHeader() {
	const [userData, setUserData] = useState(null);

	useEffect(() => {
		callMainApi("/users/123", {
			dedupeStrategy: "defer",
			onSuccess: ({ data }) => setUserData(data),
		});
	}, []);

	if (!userData) return null;

	return <h1>{userData.name}</h1>;
}

function ProfileDetails() {
	const [userData, setUserData] = useState(null);

	useEffect(() => {
		// This will reuse the in-flight request from ProfileHeader
		callMainApi("/users/123", {
			dedupeStrategy: "defer",
			onSuccess: ({ data }) => setUserData(data),
		});
	}, []);

	if (!userData) return null;

	return <div>{userData.email}</div>;
}

3. None Strategy

The none strategy disables deduplication, allowing each request to execute independently:

api.ts
const { data } = await callMainApi("/users/123", {
	dedupeStrategy: "none",
});

Custom deduplication key

By default, callApi generates a dedupeKey based on the request URL and parameters. You can customize this by providing either a static string or a callback function using the dedupeKey option:

Static deduplication key

api.ts
const { data } = await callMainApi("/users/123", {
	dedupeKey: "custom-key",
});

Dynamic deduplication key with callback

For more advanced use cases, you can provide a callback function that receives the request context and returns a custom key:

api.ts
// URL and method only - ignore headers and body
await callMainApi("/api/user/123", {
	dedupeKey: (context) => `${context.options.method}:${context.options.fullURL}`,
});

// Include specific headers in deduplication
await callMainApi("/api/data", {
	dedupeKey: (context) => {
		const authHeader = context.request.headers.get("Authorization");
		return `${context.options.fullURL}-${authHeader}`;
	},
});

// User-specific deduplication
await callMainApi("/api/dashboard", {
	dedupeKey: (context) => {
		const userId = context.options.fullURL.match(/user\/(\d+)/)?.[1];
		return `dashboard-${userId}`;
	},
});

The callback receives a RequestContext object with the following properties:

  • context.options - Merged options including URL, method, and other configuration
  • context.request - The request object with headers, body, etc.
  • context.baseConfig - Base configuration from createFetchClient
  • context.config - Instance-specific configuration

Recommendations

  • Use cancel when you only need the most recent request (most common)
  • Use defer when multiple parts of your app need the same data simultaneously
  • Use none when requests must be independent (polling, etc.)

Types

Prop

Type

Edit on GitHub

Last updated on 10/21/2025

On this page