Skip to content

Cancellation

Cancel in-flight search requests with an AbortSignal passed on SearchParams. The SDK exposes isAbortError as the canonical way to detect aborted requests.

Installation

bash
pnpm add @media-sdk/core@^0.3.0

SearchParams.signal

SearchParams accepts an optional signal: AbortSignal:

ts
interface SearchParams {
  query: string;
  page?: number;
  perPage?: number;
  signal?: AbortSignal;
  photoFilters?: PhotoSearchFilters;
  videoFilters?: VideoSearchFilters;
}

signal applies to searchPhotos and searchVideos only. Single-media lookups (getPhoto, getVideo) and getCuratedPhotos do not accept a signal in v0.3.x.

Manual cancellation (core)

ts
import { ApiKeyProvider, PexelsMediaClient } from "@media-sdk/core";

const client = new PexelsMediaClient(
  new ApiKeyProvider(process.env.PEXELS_API_KEY!),
);

const controller = new AbortController();

const searchPromise = client.searchPhotos({
  query: "nature",
  page: 1,
  perPage: 20,
  signal: controller.signal,
});

// User navigates away — cancel the request
controller.abort();

try {
  await searchPromise;
} catch (error) {
  // handle abort or other errors
}
ts
import { isAbortError } from "@media-sdk/core";

let activeController: AbortController | null = null;

async function search(query: string) {
  activeController?.abort();
  activeController = new AbortController();

  try {
    return await client.searchPhotos({
      query,
      page: 1,
      perPage: 20,
      signal: activeController.signal,
    });
  } catch (error) {
    if (isAbortError(error)) {
      return null; // superseded by a newer search
    }
    throw error;
  }
}

isAbortError

Import isAbortError from @media-sdk/core — do not rely on message strings:

ts
import { isAbortError, MediaError } from "@media-sdk/core";

try {
  await client.searchPhotos({
    query: "ocean",
    signal: controller.signal,
  });
} catch (error) {
  if (isAbortError(error)) {
    return; // expected — request was cancelled
  }

  if (error instanceof MediaError) {
    console.error(error.code, error.status);
    return;
  }

  throw error;
}

isAbortError returns true for DOMException or Error instances with name === "AbortError".

Cache and deduplication behavior

When a signaled request is aborted:

  • The in-flight request rejects with an abort error
  • Aborted responses are not cached
  • Signaled requests are not deduplicated with unsigned in-flight requests for the same cache key

This prevents stale aborted results from being served to later callers.

React hook auto-abort

useMediaSearch and useMediaVideos from @media-sdk/react@^0.3.0 automatically pass an AbortSignal and abort when:

  • The query, page, or perPage changes (a new search starts)
  • refetch() is called while a request is active
  • The component unmounts

Aborted requests are silently ignored in hook state:

  • error stays null (aborts are not surfaced as errors)
  • Stale responses cannot overwrite newer results (request-id guard)
tsx
import { useState } from "react";
import { MediaProvider, useMediaSearch } from "@media-sdk/react";
import { PexelsMediaClient, ApiKeyProvider } from "@media-sdk/core";

const client = new PexelsMediaClient(
  new ApiKeyProvider(import.meta.env.VITE_PEXELS_API_KEY),
);

function SearchInput() {
  const [query, setQuery] = useState("");

  const { data, loading, error } = useMediaSearch({
    query,
    page: 1,
    perPage: 20,
    enabled: query.length > 0,
  });

  // Changing query aborts the previous in-flight search automatically
  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      {loading && <p>Loading…</p>}
      {error && <p>Error: {error.message}</p>}
      {data && <p>{data.items.length} photos</p>}
    </>
  );
}

function SearchApp() {
  return (
    <MediaProvider client={client}>
      <SearchInput />
    </MediaProvider>
  );
}

For manual core usage outside React, replicate the pattern with your own AbortController lifecycle.