diff --git a/.changeset/use-api-holder-no-throw.md b/.changeset/use-api-holder-no-throw.md
new file mode 100644
index 0000000000..465cc9f401
--- /dev/null
+++ b/.changeset/use-api-holder-no-throw.md
@@ -0,0 +1,6 @@
+---
+'@backstage/frontend-plugin-api': patch
+'@backstage/core-plugin-api': patch
+---
+
+Changed `useApiHolder` to return an empty `ApiHolder` instead of throwing when used outside of an API context.
diff --git a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
index 4273587c00..0a899ff88a 100644
--- a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
+++ b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
@@ -114,12 +114,16 @@ describe('ApiProvider', () => {
withLogCollector(['error'], () => {
expect(() => {
render();
- }).toThrow(/^API context is not available/);
+ }).toThrow('No implementation available for apiRef{x}');
}).error,
).toEqual([
- expect.stringContaining('Error: API context is not available'),
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
expect.objectContaining({ type: 'unhandled-exception' }),
- expect.stringContaining('Error: API context is not available'),
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
expect.objectContaining({ type: 'unhandled-exception' }),
expect.stringContaining(
'The above error occurred in the component',
@@ -130,12 +134,16 @@ describe('ApiProvider', () => {
withLogCollector(['error'], () => {
expect(() => {
render();
- }).toThrow(/^API context is not available/);
+ }).toThrow('No implementation available for apiRef{x}');
}).error,
).toEqual([
- expect.stringContaining('Error: API context is not available'),
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
expect.objectContaining({ type: 'unhandled-exception' }),
- expect.stringContaining('Error: API context is not available'),
+ expect.stringContaining(
+ 'Error: No implementation available for apiRef{x}',
+ ),
expect.objectContaining({ type: 'unhandled-exception' }),
expect.stringContaining(
'The above error occurred in the component',
diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx
index 4b9d80fb62..7596105810 100644
--- a/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx
+++ b/packages/frontend-plugin-api/src/apis/system/useApi.test.tsx
@@ -17,7 +17,30 @@
import { renderHook } from '@testing-library/react';
import { createVersionedContextForTesting } from '@backstage/version-bridge';
import { createApiRef } from './ApiRef';
-import { useApi } from './useApi';
+import { useApi, useApiHolder } from './useApi';
+
+describe('useApiHolder', () => {
+ const context = createVersionedContextForTesting('api-context');
+
+ afterEach(() => {
+ context.reset();
+ });
+
+ it('should return the API holder from context', () => {
+ const holder = { get: jest.fn() };
+ context.set({ 1: holder });
+
+ const renderedHook = renderHook(() => useApiHolder());
+ expect(renderedHook.result.current).toBe(holder);
+ });
+
+ it('should return an empty API holder when there is no context', () => {
+ const renderedHook = renderHook(() => useApiHolder());
+
+ const holder = renderedHook.result.current;
+ expect(holder.get(createApiRef({ id: 'x' }))).toBeUndefined();
+ });
+});
describe('useApi', () => {
const context = createVersionedContextForTesting('api-context');
diff --git a/packages/frontend-plugin-api/src/apis/system/useApi.tsx b/packages/frontend-plugin-api/src/apis/system/useApi.tsx
index 5a8f5d5dab..2808f13f1f 100644
--- a/packages/frontend-plugin-api/src/apis/system/useApi.tsx
+++ b/packages/frontend-plugin-api/src/apis/system/useApi.tsx
@@ -19,6 +19,8 @@ import { ApiRef, ApiHolder, TypesToApiRefs } from './types';
import { useVersionedContext } from '@backstage/version-bridge';
import { NotImplementedError } from '@backstage/errors';
+const emptyApiHolder: ApiHolder = Object.freeze({ get: () => undefined });
+
/**
* React hook for retrieving {@link ApiHolder}, an API catalog.
*
@@ -27,7 +29,7 @@ import { NotImplementedError } from '@backstage/errors';
export function useApiHolder(): ApiHolder {
const versionedHolder = useVersionedContext<{ 1: ApiHolder }>('api-context');
if (!versionedHolder) {
- throw new NotImplementedError('API context is not available');
+ return emptyApiHolder;
}
const apiHolder = versionedHolder.atVersion(1);