diff --git a/packages/jsapi-bootstrap/src/DeferredApiBootstrap.test.tsx b/packages/jsapi-bootstrap/src/DeferredApiBootstrap.test.tsx new file mode 100644 index 0000000000..275a695b54 --- /dev/null +++ b/packages/jsapi-bootstrap/src/DeferredApiBootstrap.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { act, render } from '@testing-library/react'; +import type { dh as DhType } from '@deephaven/jsapi-types'; +import { TestUtils } from '@deephaven/utils'; +import DeferredApiBootstrap from './DeferredApiBootstrap'; +import { DeferredApiContext } from './useDeferredApi'; + +it('should call the error callback if no API provider wrapped', () => { + const onError = jest.fn(); + render(); + expect(onError).toHaveBeenCalled(); +}); + +it('renders children if the API is loaded', () => { + const api = TestUtils.createMockProxy(); + const { queryByText } = render( + + +
Child
+
+
+ ); + expect(queryByText('Child')).not.toBeNull(); +}); + +it('waits to render children until the API is loaded', async () => { + let resolveApi: (api: DhType) => void; + const apiPromise = new Promise(resolve => { + resolveApi = resolve; + }); + const deferredApi = jest.fn(() => apiPromise); + const options = { foo: 'bar' }; + const { queryByText } = render( + + +
Child
+
+
+ ); + expect(queryByText('Child')).toBeNull(); + expect(deferredApi).toHaveBeenCalledTimes(1); + expect(deferredApi).toHaveBeenCalledWith(options); + + const api = TestUtils.createMockProxy(); + await act(async () => { + resolveApi(api); + await apiPromise; + }); + expect(queryByText('Child')).not.toBeNull(); +}); diff --git a/packages/jsapi-bootstrap/src/DeferredApiBootstrap.tsx b/packages/jsapi-bootstrap/src/DeferredApiBootstrap.tsx new file mode 100644 index 0000000000..f5ec339cfd --- /dev/null +++ b/packages/jsapi-bootstrap/src/DeferredApiBootstrap.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import useDeferredApi from './useDeferredApi'; +import { ApiContext } from './ApiBootstrap'; + +type DeferredApiBootstrapProps = React.PropsWithChildren<{ + onError?: (error: unknown) => void; + /** + * Options to use when fetching the deferred API. + */ + options?: Record; +}>; + +/** + * Does not render children until the deferred API is resolved. + */ +export const DeferredApiBootstrap = React.memo( + ({ + children, + onError, + options, + }: DeferredApiBootstrapProps): JSX.Element | null => { + const [api, apiError] = useDeferredApi(options); + if (apiError != null) { + onError?.(apiError); + return null; + } + if (api == null) { + // Still waiting for the API to load + return null; + } + return {children}; + } +); + +DeferredApiBootstrap.displayName = 'DeferredApiBootstrap'; + +export default DeferredApiBootstrap; diff --git a/packages/jsapi-bootstrap/src/index.ts b/packages/jsapi-bootstrap/src/index.ts index e67002b81a..b529985fa7 100644 --- a/packages/jsapi-bootstrap/src/index.ts +++ b/packages/jsapi-bootstrap/src/index.ts @@ -1,4 +1,6 @@ export * from './ApiBootstrap'; export * from './ClientBootstrap'; +export * from './DeferredApiBootstrap'; export * from './useApi'; export * from './useClient'; +export * from './useDeferredApi';