diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 3e6ef7c945..8e0fc48e72 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -18,11 +18,14 @@ export { useEntityFromUrl, EntityProvider, AsyncEntityProvider, + useAsyncEntity, } from './useEntity'; export type { EntityLoadingStatus, EntityProviderProps, AsyncEntityProviderProps, + UseEntityResponse, + UseAsyncEntityResponse, } from './useEntity'; export { useEntityCompoundName } from './useEntityCompoundName'; export { diff --git a/plugins/catalog-react/src/hooks/useEntity.tsx b/plugins/catalog-react/src/hooks/useEntity.tsx index 5cb67cb79a..1db14adc22 100644 --- a/plugins/catalog-react/src/hooks/useEntity.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -128,26 +128,35 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { return { entity, loading, error, refresh }; }; +export interface UseEntityResponse { + entity: T; + /** @deprecated use useAsyncEntity instead */ + loading: boolean; + /** @deprecated use useAsyncEntity instead */ + error?: Error; + /** @deprecated use useAsyncEntity instead */ + refresh?: VoidFunction; +} + +export interface UseAsyncEntityResponse { + entity?: T; + loading: boolean; + error?: Error; + refresh?: VoidFunction; +} /** - * Grab the current entity from the context and its current loading state. + * Grab the current entity from the context, throws if the entity has not yet been loaded + * or is not available. * * @public */ -export function useEntity() { +export function useEntity(): UseEntityResponse { const versionedHolder = useVersionedContext<{ 1: EntityLoadingStatus }>('entity-context'); if (!versionedHolder) { - // TODO(Rugvip): Throw this once we fully migrate to the new context - // throw new Error('Entity context is not available'); - - return { - entity: undefined as unknown as T, - loading: true, - error: undefined, - refresh: () => {}, - }; + throw new Error('Entity context is not available'); } const value = versionedHolder.atVersion(1); @@ -155,6 +164,33 @@ export function useEntity() { throw new Error('EntityContext v1 not available'); } + if (!value.entity) { + throw new Error('Entity has not been loaded yet'); + } + + const { entity, loading, error, refresh } = value; + return { entity: entity as T, loading, error, refresh }; +} + +/** + * Grab the current entity from the context, provides loading state and errors, and the ability to refresh. + * + * @public + */ +export function useAsyncEntity< + T extends Entity = Entity, +>(): UseAsyncEntityResponse { + const versionedHolder = + useVersionedContext<{ 1: EntityLoadingStatus }>('entity-context'); + + if (!versionedHolder) { + throw new Error('Entity context is not available'); + } + const value = versionedHolder.atVersion(1); + if (!value) { + throw new Error('EntityContext v1 not available'); + } + const { entity, loading, error, refresh } = value; return { entity: entity as T, loading, error, refresh }; } diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx index 4433517247..1b8667ce2a 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; +import { useAsyncEntity } from '@backstage/plugin-catalog-react'; import React, { ReactNode, ReactElement } from 'react'; import { attachComponentData, @@ -63,7 +63,7 @@ export interface EntitySwitchProps { /** @public */ export const EntitySwitch = (props: EntitySwitchProps) => { - const { entity } = useEntity(); + const { entity } = useAsyncEntity(); const apis = useApiHolder(); const results = useElementFilter( props.children, @@ -75,6 +75,9 @@ export const EntitySwitch = (props: EntitySwitchProps) => { }) .getElements() .flatMap((element: ReactElement) => { + if (!entity) { + return []; + } const { if: condition, children: elementsChildren } = element.props as EntitySwitchCase; return [ diff --git a/plugins/catalog/src/components/EntitySwitch/conditions.ts b/plugins/catalog/src/components/EntitySwitch/conditions.ts index a02170cc6f..907aa45afe 100644 --- a/plugins/catalog/src/components/EntitySwitch/conditions.ts +++ b/plugins/catalog/src/components/EntitySwitch/conditions.ts @@ -27,7 +27,7 @@ function strCmp(a: string | undefined, b: string | undefined): boolean { * @public */ export function isKind(kind: string) { - return (entity: Entity) => strCmp(entity?.kind, kind); + return (entity: Entity) => strCmp(entity.kind, kind); } /** @@ -36,7 +36,7 @@ export function isKind(kind: string) { */ export function isComponentType(type: string) { return (entity: Entity) => { - if (!strCmp(entity?.kind, 'component')) { + if (!strCmp(entity.kind, 'component')) { return false; } const componentEntity = entity as ComponentEntity; @@ -49,5 +49,5 @@ export function isComponentType(type: string) { * @public */ export function isNamespace(namespace: string) { - return (entity: Entity) => strCmp(entity?.metadata?.namespace, namespace); + return (entity: Entity) => strCmp(entity.metadata?.namespace, namespace); }