core-plugin-api: add forwards compatibility for useApp and useRouteRef
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': patch
|
||||
---
|
||||
|
||||
The `useApp` and `useRouteRef` functions are now forwards compatible with the new frontend system. Along with the previous route reference changes this means that there is no longer a need to use `compatWrapper` from `@backstage/core-compat-api` to make code based on `@backstage/core-plugin-api` compatible with `@backstage/frontend-plugin-api` APIs.
|
||||
@@ -15,20 +15,129 @@
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { createVersionedContextForTesting } from '@backstage/version-bridge';
|
||||
import {
|
||||
appTreeApiRef,
|
||||
iconsApiRef,
|
||||
AppTreeApi,
|
||||
IconsApi,
|
||||
AppTree,
|
||||
AppNode,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { useApp } from './useApp';
|
||||
import { TestApiProvider, withLogCollector } from '@backstage/test-utils';
|
||||
|
||||
describe('v1 consumer', () => {
|
||||
const context = createVersionedContextForTesting('app-context');
|
||||
describe('useApp', () => {
|
||||
describe('old system', () => {
|
||||
const context = createVersionedContextForTesting('app-context');
|
||||
|
||||
afterEach(() => {
|
||||
context.reset();
|
||||
afterEach(() => {
|
||||
context.reset();
|
||||
});
|
||||
|
||||
it('should provide an app context', () => {
|
||||
const wrapper = ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider apis={[]}>{children}</TestApiProvider>
|
||||
);
|
||||
context.set({ 1: 'context-value' });
|
||||
|
||||
const renderedHook = renderHook(() => useApp(), { wrapper });
|
||||
expect(renderedHook.result.current).toBe('context-value');
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide an app context', () => {
|
||||
context.set({ 1: 'context-value' });
|
||||
describe('new system', () => {
|
||||
const mockIcon = () => null;
|
||||
const mockIconsApi: IconsApi = {
|
||||
getIcon: jest.fn((key: string) =>
|
||||
key === 'test-icon' ? mockIcon : undefined,
|
||||
),
|
||||
listIconKeys: jest.fn(() => ['test-icon']),
|
||||
};
|
||||
|
||||
const renderedHook = renderHook(() => useApp());
|
||||
expect(renderedHook.result.current).toBe('context-value');
|
||||
const mockPlugin = {
|
||||
id: 'test-plugin',
|
||||
};
|
||||
|
||||
const mockAppNode: AppNode = {
|
||||
spec: {
|
||||
id: 'test-node',
|
||||
attachTo: { id: 'root', input: 'children' },
|
||||
extension: {} as any,
|
||||
disabled: false,
|
||||
plugin: mockPlugin as any,
|
||||
},
|
||||
edges: {
|
||||
attachments: new Map(),
|
||||
},
|
||||
};
|
||||
|
||||
const mockAppTree: AppTree = {
|
||||
root: mockAppNode,
|
||||
nodes: new Map([['test-node', mockAppNode]]),
|
||||
orphans: [],
|
||||
};
|
||||
|
||||
const mockAppTreeApi: AppTreeApi = {
|
||||
getTree: jest.fn(() => ({ tree: mockAppTree })),
|
||||
getNodesByRoutePath: jest.fn(() => ({ nodes: [] })),
|
||||
};
|
||||
|
||||
it('should provide an app context from new app system', () => {
|
||||
const wrapper = ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[appTreeApiRef, mockAppTreeApi],
|
||||
[iconsApiRef, mockIconsApi],
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
);
|
||||
|
||||
const renderedHook = renderHook(() => useApp(), { wrapper });
|
||||
|
||||
const appContext = renderedHook.result.current;
|
||||
expect(appContext).toBeDefined();
|
||||
expect(appContext.getPlugins()).toHaveLength(1);
|
||||
expect(appContext.getPlugins()[0].getId()).toBe('test-plugin');
|
||||
expect(appContext.getSystemIcon('test-icon')).toBe(mockIcon);
|
||||
expect(appContext.getSystemIcons()).toEqual({ 'test-icon': mockIcon });
|
||||
expect(appContext.getComponents().Progress).toBeDefined();
|
||||
expect(appContext.getComponents().NotFoundErrorPage).toBeDefined();
|
||||
});
|
||||
|
||||
it('should error on missing appTreeApi or iconsApi', () => {
|
||||
withLogCollector(['error'], () => {
|
||||
expect(() =>
|
||||
renderHook(() => useApp(), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider apis={[]}>{children}</TestApiProvider>
|
||||
),
|
||||
}),
|
||||
).toThrow('App context is not available');
|
||||
|
||||
expect(() =>
|
||||
renderHook(() => useApp(), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider apis={[[appTreeApiRef, mockAppTreeApi]]}>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
}),
|
||||
).toThrow('App context is not available');
|
||||
|
||||
expect(() =>
|
||||
renderHook(() => useApp(), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider apis={[[iconsApiRef, mockIconsApi]]}>
|
||||
{children}
|
||||
</TestApiProvider>
|
||||
),
|
||||
}),
|
||||
).toThrow('App context is not available');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,18 +14,150 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useVersionedContext } from '@backstage/version-bridge';
|
||||
import {
|
||||
appTreeApiRef,
|
||||
iconsApiRef,
|
||||
useApiHolder,
|
||||
ErrorDisplay,
|
||||
NotFoundErrorPage,
|
||||
Progress,
|
||||
createFrontendPlugin,
|
||||
FrontendPlugin,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
AppComponents,
|
||||
IconComponent,
|
||||
BackstagePlugin,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { getOrCreateGlobalSingleton } from '@backstage/version-bridge';
|
||||
import { AppContext as AppContextV1 } from './types';
|
||||
|
||||
const legacyPluginStore = getOrCreateGlobalSingleton(
|
||||
'legacy-plugin-compatibility-store',
|
||||
() => new WeakMap<FrontendPlugin, BackstagePlugin>(),
|
||||
);
|
||||
|
||||
function toLegacyPlugin(plugin: FrontendPlugin): BackstagePlugin {
|
||||
let legacy = legacyPluginStore.get(plugin);
|
||||
if (legacy) {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
const errorMsg = 'Not implemented in legacy plugin compatibility layer';
|
||||
const notImplemented = () => {
|
||||
throw new Error(errorMsg);
|
||||
};
|
||||
|
||||
legacy = {
|
||||
getId(): string {
|
||||
return plugin.id;
|
||||
},
|
||||
get routes() {
|
||||
return {};
|
||||
},
|
||||
get externalRoutes() {
|
||||
return {};
|
||||
},
|
||||
getApis: notImplemented,
|
||||
getFeatureFlags: notImplemented,
|
||||
provide: notImplemented,
|
||||
};
|
||||
|
||||
legacyPluginStore.set(plugin, legacy);
|
||||
return legacy;
|
||||
}
|
||||
|
||||
function toNewPlugin(plugin: BackstagePlugin): FrontendPlugin {
|
||||
return createFrontendPlugin({
|
||||
pluginId: plugin.getId(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook providing {@link AppContext}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const useApp = (): AppContextV1 => {
|
||||
const apiHolder = useApiHolder();
|
||||
const appTreeApi = apiHolder.get(appTreeApiRef);
|
||||
const versionedContext = useVersionedContext<{ 1: AppContextV1 }>(
|
||||
'app-context',
|
||||
);
|
||||
|
||||
const newAppContext = useMemo<AppContextV1 | null>(() => {
|
||||
if (!appTreeApi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const iconsApi = apiHolder.get(iconsApiRef);
|
||||
if (!iconsApi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { tree } = appTreeApi.getTree();
|
||||
|
||||
let gatheredPlugins: BackstagePlugin[] | undefined = undefined;
|
||||
|
||||
const ErrorBoundaryFallbackWrapper: AppComponents['ErrorBoundaryFallback'] =
|
||||
({ plugin, ...rest }) => (
|
||||
<ErrorDisplay {...rest} plugin={plugin && toNewPlugin(plugin)} />
|
||||
);
|
||||
|
||||
return {
|
||||
getPlugins(): BackstagePlugin[] {
|
||||
if (gatheredPlugins) {
|
||||
return gatheredPlugins;
|
||||
}
|
||||
|
||||
const pluginSet = new Set<BackstagePlugin>();
|
||||
for (const node of tree.nodes.values()) {
|
||||
const plugin = node.spec.plugin;
|
||||
if (plugin) {
|
||||
pluginSet.add(toLegacyPlugin(plugin));
|
||||
}
|
||||
}
|
||||
gatheredPlugins = Array.from(pluginSet);
|
||||
|
||||
return gatheredPlugins;
|
||||
},
|
||||
|
||||
getSystemIcon(key: string): IconComponent | undefined {
|
||||
return iconsApi.getIcon(key);
|
||||
},
|
||||
|
||||
getSystemIcons(): Record<string, IconComponent> {
|
||||
return Object.fromEntries(
|
||||
iconsApi.listIconKeys().map(key => [key, iconsApi.getIcon(key)!]),
|
||||
);
|
||||
},
|
||||
|
||||
getComponents(): AppComponents {
|
||||
return {
|
||||
NotFoundErrorPage: NotFoundErrorPage,
|
||||
BootErrorPage() {
|
||||
throw new Error(
|
||||
'The BootErrorPage app component should not be accessed by plugins',
|
||||
);
|
||||
},
|
||||
Progress: Progress,
|
||||
Router() {
|
||||
throw new Error(
|
||||
'The Router app component should not be accessed by plugins',
|
||||
);
|
||||
},
|
||||
ErrorBoundaryFallback: ErrorBoundaryFallbackWrapper,
|
||||
};
|
||||
},
|
||||
};
|
||||
}, [appTreeApi, apiHolder]);
|
||||
|
||||
if (newAppContext) {
|
||||
return newAppContext;
|
||||
}
|
||||
|
||||
if (!versionedContext) {
|
||||
throw new Error('App context is not available');
|
||||
}
|
||||
|
||||
@@ -18,140 +18,203 @@ import { renderHook } from '@testing-library/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { MemoryRouter, Router } from 'react-router-dom';
|
||||
import { createVersionedContextForTesting } from '@backstage/version-bridge';
|
||||
import {
|
||||
routeResolutionApiRef,
|
||||
RouteResolutionApi,
|
||||
RouteFunc,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { useRouteRef } from './useRouteRef';
|
||||
import { createRouteRef } from './RouteRef';
|
||||
import { createExternalRouteRef } from './ExternalRouteRef';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
describe('v1 consumer', () => {
|
||||
const context = createVersionedContextForTesting('routing-context');
|
||||
describe('useRouteRef', () => {
|
||||
describe('old app system', () => {
|
||||
const context = createVersionedContextForTesting('routing-context');
|
||||
|
||||
afterEach(() => {
|
||||
context.reset();
|
||||
});
|
||||
|
||||
it('should resolve routes', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
|
||||
const renderedHook = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<MemoryRouter initialEntries={['/my-page']} children={children} />
|
||||
),
|
||||
afterEach(() => {
|
||||
context.reset();
|
||||
});
|
||||
|
||||
const routeFunc = renderedHook.result.current;
|
||||
expect(routeFunc()).toBe('/hello');
|
||||
expect(resolve).toHaveBeenCalledWith(
|
||||
routeRef,
|
||||
expect.objectContaining({
|
||||
pathname: '/my-page',
|
||||
}),
|
||||
);
|
||||
});
|
||||
it('should resolve routes', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
|
||||
it('re-resolves the routeFunc when the search parameters change', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
const renderedHook = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<MemoryRouter initialEntries={['/my-page']} children={children} />
|
||||
),
|
||||
});
|
||||
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
const routeFunc = renderedHook.result.current;
|
||||
expect(routeFunc()).toBe('/hello');
|
||||
expect(resolve).toHaveBeenCalledWith(
|
||||
routeRef,
|
||||
expect.objectContaining({
|
||||
pathname: '/my-page',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
it('re-resolves the routeFunc when the search parameters change', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
|
||||
history.push('/my-new-page');
|
||||
rerender();
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
it('does not re-resolve the routeFunc the location pathname does not change', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
history.push('/my-new-page');
|
||||
rerender();
|
||||
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
expect(resolve).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
it('does not re-resolve the routeFunc the location pathname does not change', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
|
||||
history.push('/my-page');
|
||||
rerender();
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
it('does not re-resolve the routeFunc when the search parameter changes', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
history.push('/my-page');
|
||||
rerender();
|
||||
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
it('does not re-resolve the routeFunc when the search parameter changes', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
|
||||
history.push('/my-page?foo=bar');
|
||||
rerender();
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
it('does not re-resolve the routeFunc when the hash parameter changes', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
history.push('/my-page?foo=bar');
|
||||
rerender();
|
||||
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
it('does not re-resolve the routeFunc when the hash parameter changes', () => {
|
||||
const resolve = jest.fn(() => () => '/hello');
|
||||
context.set({ 1: { resolve } });
|
||||
|
||||
history.push('/my-page#foo');
|
||||
rerender();
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const history = createBrowserHistory();
|
||||
history.push('/my-page');
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
const { rerender } = renderHook(() => useRouteRef(routeRef), {
|
||||
wrapper: ({ children }: PropsWithChildren<{}>) => (
|
||||
<Router
|
||||
location={history.location}
|
||||
navigator={history}
|
||||
children={children}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
|
||||
history.push('/my-page#foo');
|
||||
rerender();
|
||||
|
||||
expect(resolve).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('new app system', () => {
|
||||
it('should resolve routes using routeResolutionApi', () => {
|
||||
const routeRef = createRouteRef({ id: 'ref1' });
|
||||
const mockRouteFunc: RouteFunc<any> = jest.fn(() => '/new-route');
|
||||
|
||||
const mockRouteResolutionApi: RouteResolutionApi = {
|
||||
resolve: jest.fn(() => mockRouteFunc),
|
||||
};
|
||||
|
||||
const wrapper = ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider
|
||||
apis={[[routeResolutionApiRef, mockRouteResolutionApi]]}
|
||||
>
|
||||
<MemoryRouter initialEntries={['/my-page']}>{children}</MemoryRouter>
|
||||
</TestApiProvider>
|
||||
);
|
||||
|
||||
const renderedHook = renderHook(() => useRouteRef(routeRef), { wrapper });
|
||||
|
||||
const routeFunc = renderedHook.result.current;
|
||||
expect(routeFunc).toBe(mockRouteFunc);
|
||||
expect(mockRouteResolutionApi.resolve).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ sourcePath: '/my-page' },
|
||||
);
|
||||
expect(routeFunc()).toBe('/new-route');
|
||||
});
|
||||
|
||||
it('should handle optional external route refs', () => {
|
||||
const externalRouteRef = createExternalRouteRef({
|
||||
id: 'external-ref',
|
||||
optional: true,
|
||||
});
|
||||
|
||||
const mockRouteResolutionApi: RouteResolutionApi = {
|
||||
resolve: jest.fn(() => undefined),
|
||||
};
|
||||
|
||||
const wrapper = ({ children }: PropsWithChildren<{}>) => (
|
||||
<TestApiProvider
|
||||
apis={[[routeResolutionApiRef, mockRouteResolutionApi]]}
|
||||
>
|
||||
<MemoryRouter initialEntries={['/my-page']}>{children}</MemoryRouter>
|
||||
</TestApiProvider>
|
||||
);
|
||||
|
||||
const renderedHook = renderHook(() => useRouteRef(externalRouteRef), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
expect(renderedHook.result.current).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { matchRoutes, useLocation } from 'react-router-dom';
|
||||
import { useVersionedContext } from '@backstage/version-bridge';
|
||||
import {
|
||||
routeResolutionApiRef,
|
||||
useApiHolder,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
AnyParams,
|
||||
ExternalRouteRef,
|
||||
@@ -86,30 +90,53 @@ export function useRouteRef<Params extends AnyParams>(
|
||||
| ExternalRouteRef<Params, any>,
|
||||
): RouteFunc<Params> | undefined {
|
||||
const { pathname } = useLocation();
|
||||
const apiHolder = useApiHolder();
|
||||
const routeResolutionApi = apiHolder.get(routeResolutionApiRef);
|
||||
const versionedContext = useVersionedContext<{ 1: RouteResolver }>(
|
||||
'routing-context',
|
||||
);
|
||||
if (!versionedContext) {
|
||||
throw new Error('Routing context is not available');
|
||||
}
|
||||
|
||||
const resolver = versionedContext.atVersion(1);
|
||||
const routeFunc = useMemo(
|
||||
const resolver = versionedContext?.atVersion(1);
|
||||
|
||||
const newRouteFunc = useMemo(() => {
|
||||
if (!routeResolutionApi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return routeResolutionApi.resolve(routeRef, {
|
||||
sourcePath: pathname,
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, [routeResolutionApi, routeRef, pathname]);
|
||||
|
||||
const legacyRouteFunc = useMemo(
|
||||
() => resolver && resolver.resolve(routeRef, { pathname }),
|
||||
[resolver, routeRef, pathname],
|
||||
);
|
||||
|
||||
if (!versionedContext) {
|
||||
throw new Error('useRouteRef used outside of routing context');
|
||||
if (newRouteFunc !== null) {
|
||||
const isOptional = 'optional' in routeRef && routeRef.optional;
|
||||
if (!newRouteFunc && !isOptional) {
|
||||
throw new Error(`No path for ${routeRef}`);
|
||||
}
|
||||
return newRouteFunc;
|
||||
}
|
||||
|
||||
if (!versionedContext) {
|
||||
throw new Error('Routing context is not available');
|
||||
}
|
||||
|
||||
if (!resolver) {
|
||||
throw new Error('RoutingContext v1 not available');
|
||||
}
|
||||
|
||||
const isOptional = 'optional' in routeRef && routeRef.optional;
|
||||
if (!routeFunc && !isOptional) {
|
||||
if (!legacyRouteFunc && !isOptional) {
|
||||
throw new Error(`No path for ${routeRef}`);
|
||||
}
|
||||
|
||||
return routeFunc;
|
||||
return legacyRouteFunc;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user