Make utility hook responses async, allowing addons to handle errors independently.

Co-authored-by: Emma Indal <emma.indahl@gmail.com>
Co-authored-by: Anders Näsman <andersn@spotify.com>

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2022-03-16 14:09:14 +01:00
committed by Emma Indal
parent d8db6db3d7
commit e328e3d31b
5 changed files with 53 additions and 35 deletions
+12 -6
View File
@@ -5,6 +5,7 @@
```ts
/// <reference types="react" />
import { AsyncState } from 'react-use/lib/useAsyncFn';
import { ComponentType } from 'react';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { Extension } from '@backstage/core-plugin-api';
@@ -17,6 +18,9 @@ export function createTechDocsAddon<TComponentProps>(
options: TechDocsAddonOptions<TComponentProps>,
): Extension<ComponentType<TComponentProps>>;
// @public
export type TechDocsAddonAsyncMetadata<TValue> = AsyncState<TValue | undefined>;
// @public
export enum TechDocsAddonLocations {
COMPONENT = 'component',
@@ -48,16 +52,18 @@ export type TechDocsReaderPageProps = {
};
// @public
export const useEntityMetadata: () => TechDocsEntityMetadata | undefined;
// @public
export const useMetadata: () => TechDocsMetadata | undefined;
export const useEntityMetadata: () => TechDocsAddonAsyncMetadata<TechDocsEntityMetadata>;
// @public
export const useShadowRoot: () => ShadowRoot | undefined;
// @public
export const useShadowRootElements: <T extends HTMLElement = HTMLElement>(
export const useShadowRootElements: <
TReturnedElement extends HTMLElement = HTMLElement,
>(
selectors: string[],
) => T[];
) => TReturnedElement[];
// @public
export const useTechDocsMetadata: () => TechDocsAddonAsyncMetadata<TechDocsMetadata>;
```
@@ -22,7 +22,7 @@ import React, { useEffect } from 'react';
import Helmet from 'react-helmet';
import { useTechDocsAddons } from '../../addons';
import { useMetadata, useTechDocsReaderPage } from '../../context';
import { useTechDocsMetadata, useTechDocsReaderPage } from '../../context';
import { TechDocsAddonLocations as locations } from '../../types';
const skeleton = <Skeleton animation="wave" variant="text" height={40} />;
@@ -31,7 +31,7 @@ export const TechDocsReaderPageHeader = () => {
const addons = useTechDocsAddons();
const configApi = useApi(configApiRef);
const metadata = useMetadata();
const { value: metadata } = useTechDocsMetadata();
const { title, setTitle, subtitle, setSubtitle } = useTechDocsReaderPage();
+26 -25
View File
@@ -15,7 +15,7 @@
*/
import { CompoundEntityRef } from '@backstage/catalog-model';
import { useApi, useApp } from '@backstage/core-plugin-api';
import { useApi } from '@backstage/core-plugin-api';
import {
techdocsApiRef,
TechDocsEntityMetadata,
@@ -30,30 +30,33 @@ import React, {
useState,
} from 'react';
import useAsync from 'react-use/lib/useAsync';
import { TechDocsAddonAsyncMetadata } from './types';
type PropsWithEntityName = PropsWithChildren<{ entityName: CompoundEntityRef }>;
const TechDocsMetadataContext = createContext<TechDocsMetadata | undefined>(
undefined,
);
const initialContextValue = {
loading: true,
error: undefined,
value: undefined,
};
const TechDocsMetadataContext =
createContext<TechDocsAddonAsyncMetadata<TechDocsMetadata>>(
initialContextValue,
);
export const TechDocsMetadataProvider = ({
entityName,
children,
}: PropsWithEntityName) => {
const { NotFoundErrorPage } = useApp().getComponents();
const techdocsApi = useApi(techdocsApiRef);
const { value, loading, error } = useAsync(async () => {
const metadataResponse = useAsync(async () => {
return await techdocsApi.getTechDocsMetadata(entityName);
}, []);
if (!loading && error) {
return <NotFoundErrorPage />;
}
return (
<TechDocsMetadataContext.Provider value={value}>
<TechDocsMetadataContext.Provider value={metadataResponse}>
{children}
</TechDocsMetadataContext.Provider>
);
@@ -64,31 +67,27 @@ export const TechDocsMetadataProvider = ({
* current TechDocs site.
* @public
*/
export const useMetadata = () => {
export const useTechDocsMetadata = () => {
return useContext(TechDocsMetadataContext);
};
const TechDocsEntityContext = createContext<TechDocsEntityMetadata | undefined>(
undefined,
);
const TechDocsEntityContext =
createContext<TechDocsAddonAsyncMetadata<TechDocsEntityMetadata>>(
initialContextValue,
);
export const TechDocsEntityProvider = ({
entityName,
children,
}: PropsWithEntityName) => {
const { NotFoundErrorPage } = useApp().getComponents();
const techdocsApi = useApi(techdocsApiRef);
const { value, loading, error } = useAsync(async () => {
const metadataResponse = useAsync(async () => {
return await techdocsApi.getEntityMetadata(entityName);
}, []);
if (!loading && error) {
return <NotFoundErrorPage />;
}
return (
<TechDocsEntityContext.Provider value={value}>
<TechDocsEntityContext.Provider value={metadataResponse}>
{children}
</TechDocsEntityContext.Provider>
);
@@ -180,13 +179,15 @@ export const useShadowRoot = () => {
*
* @public
*/
export const useShadowRootElements = <T extends HTMLElement = HTMLElement>(
export const useShadowRootElements = <
TReturnedElement extends HTMLElement = HTMLElement,
>(
selectors: string[],
): T[] => {
): TReturnedElement[] => {
const shadowRoot = useShadowRoot();
if (!shadowRoot) return [];
return selectors
.map(selector => shadowRoot?.querySelectorAll<T>(selector))
.map(selector => shadowRoot?.querySelectorAll<TReturnedElement>(selector))
.filter(nodeList => nodeList.length)
.map(nodeList => Array.from(nodeList))
.flat();
+6 -2
View File
@@ -24,8 +24,12 @@ export { createTechDocsAddon, TechDocsAddons } from './addons';
export * from './components';
export {
useEntityMetadata,
useMetadata,
useTechDocsMetadata,
useShadowRoot,
useShadowRootElements,
} from './context';
export type { TechDocsAddonLocations, TechDocsAddonOptions } from './types';
export type {
TechDocsAddonAsyncMetadata,
TechDocsAddonLocations,
TechDocsAddonOptions,
} from './types';
+7
View File
@@ -15,6 +15,7 @@
*/
import { ComponentType } from 'react';
import { AsyncState } from 'react-use/lib/useAsyncFn';
/**
* Locations for which TechDocs addons may be declared and rendered.
@@ -70,3 +71,9 @@ export type TechDocsAddonOptions<TAddonProps = {}> = {
location: TechDocsAddonLocations;
component: ComponentType<TAddonProps>;
};
/**
* Common response envelope for addon-related hooks.
* @public
*/
export type TechDocsAddonAsyncMetadata<TValue> = AsyncState<TValue | undefined>;