From ec494101a6aff31784a0cdbbab082d43d0962bcc Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Fri, 10 Jan 2025 13:37:56 -0700 Subject: [PATCH] fix(app): Inject labware definitions into Error Recovery (#17248) Closes RQA-3814 --- .../RunHeaderModalContainer.tsx | 1 + .../ErrorRecoveryFlows/__fixtures__/index.ts | 1 + .../__tests__/ErrorRecoveryFlows.test.tsx | 25 ++++++++- .../hooks/__tests__/useDeckMapUtils.test.ts | 24 +++------ .../hooks/useDeckMapUtils.ts | 52 ++++++++---------- .../ErrorRecoveryFlows/hooks/useERUtils.ts | 11 ++-- .../organisms/ErrorRecoveryFlows/index.tsx | 53 +++++++++++++------ .../__tests__/RunningProtocol.test.tsx | 2 + app/src/pages/ODD/RunningProtocol/index.tsx | 6 ++- .../useRunLoadedLabwareDefinitionsByUri.ts | 24 +++++---- 10 files changed, 116 insertions(+), 83 deletions(-) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx index 0c306339f69..8486296122b 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx @@ -52,6 +52,7 @@ export function RunHeaderModalContainer( runStatus={runStatus} runId={runId} unvalidatedFailedCommand={recoveryModalUtils.failedCommand} + runLwDefsByUri={recoveryModalUtils.runLwDefsByUri} protocolAnalysis={robotProtocolAnalysis} /> ) : null} diff --git a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts index 1a815b99c1e..aaf0b89e062 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts +++ b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts @@ -58,6 +58,7 @@ export const mockRecoveryContentProps: RecoveryContentProps = { byRunRecord: mockFailedCommand, byAnalysis: mockFailedCommand, }, + runLwDefsByUri: {} as any, errorKind: 'GENERAL_ERROR', robotType: FLEX_ROBOT_TYPE, runId: 'MOCK_RUN_ID', diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx index 53bc8c15a8b..47f1668af87 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx @@ -45,6 +45,7 @@ vi.mock('react-redux', async () => { describe('useErrorRecoveryFlows', () => { beforeEach(() => { vi.mocked(useCurrentlyRecoveringFrom).mockReturnValue('mockCommand' as any) + vi.mocked(useRunLoadedLabwareDefinitionsByUri).mockReturnValue({}) }) it('should have initial state of isERActive as false', () => { @@ -89,12 +90,32 @@ describe('useErrorRecoveryFlows', () => { expect(result.current.failedCommand).toEqual('mockCommand') }) + it("should return the run's labware definitions", () => { + const { result } = renderHook(() => + useErrorRecoveryFlows('MOCK_ID', RUN_STATUS_RUNNING) + ) + + expect(result.current.failedCommand).toEqual('mockCommand') + }) + it(`should return isERActive false if the run status is ${RUN_STATUS_STOP_REQUESTED} before seeing ${RUN_STATUS_AWAITING_RECOVERY}`, () => { const { result } = renderHook(() => useErrorRecoveryFlows('MOCK_ID', RUN_STATUS_STOP_REQUESTED) ) - expect(result.current.isERActive).toEqual(false) + expect(result.current.runLwDefsByUri).toEqual({}) + }) + + it('should not return isERActive if the run labware defintions is null', () => { + vi.mocked(useRunLoadedLabwareDefinitionsByUri).mockReturnValue(null) + + const { result } = renderHook( + runStatus => useErrorRecoveryFlows('MOCK_ID', runStatus), + { + initialProps: RUN_STATUS_AWAITING_RECOVERY, + } + ) + expect(result.current.isERActive).toBe(false) }) it('should set hasSeenAwaitingRecovery to true when runStatus is RUN_STATUS_AWAITING_RECOVERY', () => { @@ -143,6 +164,7 @@ describe('ErrorRecoveryFlows', () => { unvalidatedFailedCommand: mockFailedCommand, runId: 'MOCK_RUN_ID', protocolAnalysis: null, + runLwDefsByUri: {}, } vi.mocked(ErrorRecoveryWizard).mockReturnValue(
MOCK WIZARD
) vi.mocked(RecoverySplash).mockReturnValue(
MOCK RUN PAUSED SPLASH
) @@ -167,7 +189,6 @@ describe('ErrorRecoveryFlows', () => { intent: 'recovering', showTakeover: false, }) - vi.mocked(useRunLoadedLabwareDefinitionsByUri).mockReturnValue({}) }) it('renders the wizard when showERWizard is true', () => { diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts index 1a6d07ba634..5b6855a9e7d 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts @@ -198,17 +198,7 @@ describe('getRunCurrentModulesInfo', () => { const result = getRunCurrentModulesInfo({ runRecord: null as any, deckDef: mockDeckDef, - labwareDefinitionsByUri: {}, - }) - - expect(result).toEqual([]) - }) - - it('should return an empty array if protocolAnalysis is null', () => { - const result = getRunCurrentModulesInfo({ - runRecord: mockRunRecord, - deckDef: mockDeckDef, - labwareDefinitionsByUri: null, + runLwDefsByUri: {}, }) expect(result).toEqual([]) @@ -219,7 +209,7 @@ describe('getRunCurrentModulesInfo', () => { const result = getRunCurrentModulesInfo({ runRecord: mockRunRecord, deckDef: mockDeckDef, - labwareDefinitionsByUri: { + runLwDefsByUri: { 'opentrons/opentrons_96_pcr_adapter/1': 'MOCK_LW_DEF', } as any, }) @@ -242,7 +232,7 @@ describe('getRunCurrentModulesInfo', () => { data: { modules: [mockModule], labware: [] }, }, deckDef: mockDeckDef, - labwareDefinitionsByUri: {}, + runLwDefsByUri: {}, }) expect(result).toEqual([ { @@ -261,7 +251,7 @@ describe('getRunCurrentModulesInfo', () => { const result = getRunCurrentModulesInfo({ runRecord: mockRunRecord, deckDef: mockDeckDef, - labwareDefinitionsByUri: null, + runLwDefsByUri: {}, }) expect(result).toEqual([]) }) @@ -286,7 +276,7 @@ describe('getRunCurrentLabwareInfo', () => { it('should return an empty array if runRecord is null', () => { const result = getRunCurrentLabwareInfo({ runRecord: undefined, - labwareDefinitionsByUri: {} as any, + runLwDefsByUri: {} as any, }) expect(result).toEqual([]) @@ -295,7 +285,7 @@ describe('getRunCurrentLabwareInfo', () => { it('should return an empty array if protocolAnalysis is null', () => { const result = getRunCurrentLabwareInfo({ runRecord: { data: { labware: [] } } as any, - labwareDefinitionsByUri: null, + runLwDefsByUri: {}, }) expect(result).toEqual([]) @@ -309,7 +299,7 @@ describe('getRunCurrentLabwareInfo', () => { const result = getRunCurrentLabwareInfo({ runRecord: { data: { labware: [mockPickUpTipLwSlotName] } } as any, - labwareDefinitionsByUri: { + runLwDefsByUri: { [mockPickUpTipLabware.definitionUri]: mockLabwareDef, }, }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts index 458747f5b07..22dd645835a 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts @@ -42,7 +42,7 @@ interface UseDeckMapUtilsProps { runId: ErrorRecoveryFlowsProps['runId'] protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis'] failedLabwareUtils: UseFailedLabwareUtilsResult - labwareDefinitionsByUri: ERUtilsProps['labwareDefinitionsByUri'] + runLwDefsByUri: ERUtilsProps['runLwDefsByUri'] runRecord: Run | undefined } @@ -65,7 +65,7 @@ export function useDeckMapUtils({ runRecord, runId, failedLabwareUtils, - labwareDefinitionsByUri, + runLwDefsByUri, }: UseDeckMapUtilsProps): UseDeckMapUtilsResult { const robotType = protocolAnalysis?.robotType ?? OT2_ROBOT_TYPE const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis) @@ -78,9 +78,9 @@ export function useDeckMapUtils({ getRunCurrentModulesInfo({ runRecord, deckDef, - labwareDefinitionsByUri, + runLwDefsByUri, }), - [runRecord, deckDef, labwareDefinitionsByUri] + [runRecord, deckDef, runLwDefsByUri] ) const runCurrentModules = useMemo( @@ -94,8 +94,8 @@ export function useDeckMapUtils({ ) const currentLabwareInfo = useMemo( - () => getRunCurrentLabwareInfo({ runRecord, labwareDefinitionsByUri }), - [runRecord, labwareDefinitionsByUri] + () => getRunCurrentLabwareInfo({ runRecord, runLwDefsByUri }), + [runRecord, runLwDefsByUri] ) const { updatedModules, remainingLabware } = useMemo( @@ -114,32 +114,24 @@ export function useDeckMapUtils({ ) const movedLabwareDef = - labwareDefinitionsByUri != null && failedLabwareUtils.failedLabware != null - ? labwareDefinitionsByUri[failedLabwareUtils.failedLabware.definitionUri] + runLwDefsByUri != null && failedLabwareUtils.failedLabware != null + ? runLwDefsByUri[failedLabwareUtils.failedLabware.definitionUri] : null const moduleRenderInfo = useMemo( () => - runRecord != null && labwareDefinitionsByUri != null - ? getRunModuleRenderInfo( - runRecord.data, - deckDef, - labwareDefinitionsByUri - ) + runRecord != null && runLwDefsByUri != null + ? getRunModuleRenderInfo(runRecord.data, deckDef, runLwDefsByUri) : [], - [deckDef, labwareDefinitionsByUri, runRecord] + [deckDef, runLwDefsByUri, runRecord] ) const labwareRenderInfo = useMemo( () => - runRecord != null && labwareDefinitionsByUri != null - ? getRunLabwareRenderInfo( - runRecord.data, - labwareDefinitionsByUri, - deckDef - ) + runRecord != null && runLwDefsByUri != null + ? getRunLabwareRenderInfo(runRecord.data, runLwDefsByUri, deckDef) : [], - [deckDef, labwareDefinitionsByUri, runRecord] + [deckDef, runLwDefsByUri, runRecord] ) return { @@ -258,13 +250,13 @@ interface RunCurrentModuleInfo { export const getRunCurrentModulesInfo = ({ runRecord, deckDef, - labwareDefinitionsByUri, + runLwDefsByUri, }: { runRecord: UseDeckMapUtilsProps['runRecord'] deckDef: DeckDefinition - labwareDefinitionsByUri?: LabwareDefinitionsByUri | null + runLwDefsByUri: UseDeckMapUtilsProps['runLwDefsByUri'] }): RunCurrentModuleInfo[] => { - if (runRecord == null || labwareDefinitionsByUri == null) { + if (runRecord == null) { return [] } else { return runRecord.data.modules.reduce( @@ -281,7 +273,7 @@ export const getRunCurrentModulesInfo = ({ const nestedLabwareDef = nestedLabware != null - ? labwareDefinitionsByUri[nestedLabware.definitionUri] + ? runLwDefsByUri[nestedLabware.definitionUri] : null const slotPosition = getPositionFromSlotId( @@ -325,12 +317,12 @@ interface RunCurrentLabwareInfo { // Derive the labware info necessary to render labware on the deck. export function getRunCurrentLabwareInfo({ runRecord, - labwareDefinitionsByUri, + runLwDefsByUri, }: { runRecord: UseDeckMapUtilsProps['runRecord'] - labwareDefinitionsByUri?: LabwareDefinitionsByUri | null + runLwDefsByUri: UseDeckMapUtilsProps['runLwDefsByUri'] }): RunCurrentLabwareInfo[] { - if (runRecord == null || labwareDefinitionsByUri == null) { + if (runRecord == null) { return [] } else { const allLabware = runRecord.data.labware.reduce( @@ -341,7 +333,7 @@ export function getRunCurrentLabwareInfo({ runRecord, true ) // Exclude modules since handled separately. - const labwareDef = getLabwareDefinition(lw, labwareDefinitionsByUri) + const labwareDef = getLabwareDefinition(lw, runLwDefsByUri) if (slotName == null || labwareLocation == null) { return acc diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts index 72e9bb481bc..4bd4a93643f 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts @@ -22,11 +22,7 @@ import { useCleanupRecoveryState } from './useCleanupRecoveryState' import { useFailedPipetteUtils } from './useFailedPipetteUtils' import { getRunningStepCountsFrom } from '/app/resources/protocols' -import type { - LabwareDefinition2, - LabwareDefinitionsByUri, - RobotType, -} from '@opentrons/shared-data' +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' import type { IRecoveryMap, RouteStep, RecoveryRoute } from '../types' import type { ErrorRecoveryFlowsProps } from '..' import type { UseRouteUpdateActionsResult } from './useRouteUpdateActions' @@ -54,7 +50,6 @@ export type ERUtilsProps = Omit & { failedCommand: ReturnType isActiveUser: UseRecoveryTakeoverResult['isActiveUser'] allRunDefs: LabwareDefinition2[] - labwareDefinitionsByUri: LabwareDefinitionsByUri | null } export interface ERUtilsResults { @@ -90,7 +85,7 @@ export function useERUtils({ isActiveUser, allRunDefs, unvalidatedFailedCommand, - labwareDefinitionsByUri, + runLwDefsByUri, }: ERUtilsProps): ERUtilsResults { const { data: attachedInstruments } = useInstrumentsQuery() const { data: runRecord } = useNotifyRunQuery(runId) @@ -185,7 +180,7 @@ export function useERUtils({ runRecord, protocolAnalysis, failedLabwareUtils, - labwareDefinitionsByUri, + runLwDefsByUri, }) const recoveryActionMutationUtils = useRecoveryActionMutation( diff --git a/app/src/organisms/ErrorRecoveryFlows/index.tsx b/app/src/organisms/ErrorRecoveryFlows/index.tsx index 84938e2db77..24834c86305 100644 --- a/app/src/organisms/ErrorRecoveryFlows/index.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/index.tsx @@ -28,11 +28,12 @@ import { useRecoveryTakeover, useRetainedFailedCommandBySource, } from './hooks' +import { useRunLoadedLabwareDefinitionsByUri } from '/app/resources/runs' import type { RunStatus } from '@opentrons/api-client' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' import type { FailedCommand } from './types' -import { useRunLoadedLabwareDefinitionsByUri } from '/app/resources/runs' +import type { RunLoadedLabwareDefinitionsByUri } from '/app/resources/runs' const VALID_ER_RUN_STATUSES: RunStatus[] = [ RUN_STATUS_AWAITING_RECOVERY, @@ -53,10 +54,24 @@ const INVALID_ER_RUN_STATUSES: RunStatus[] = [ RUN_STATUS_IDLE, ] -export interface UseErrorRecoveryResult { +interface UseErrorRecoveryResultBase { isERActive: boolean failedCommand: FailedCommand | null + runLwDefsByUri: ReturnType +} +export interface UseErrorRecoveryActiveResult + extends UseErrorRecoveryResultBase { + isERActive: true + failedCommand: FailedCommand + runLwDefsByUri: RunLoadedLabwareDefinitionsByUri +} +export interface UseErrorRecoveryInactiveResult + extends UseErrorRecoveryResultBase { + isERActive: false } +export type UseErrorRecoveryResult = + | UseErrorRecoveryInactiveResult + | UseErrorRecoveryActiveResult export function useErrorRecoveryFlows( runId: string, @@ -64,6 +79,7 @@ export function useErrorRecoveryFlows( ): UseErrorRecoveryResult { const [isERActive, setIsERActive] = useState(false) const failedCommand = useCurrentlyRecoveringFrom(runId, runStatus) + const runLwDefsByUri = useRunLoadedLabwareDefinitionsByUri(runId) // The complexity of this logic exists to persist Error Recovery screens past the server's definition of Error Recovery. // Ex, show a "cancelling run" modal in Error Recovery flows despite the robot no longer being in a recoverable state. @@ -85,8 +101,7 @@ export function useErrorRecoveryFlows( if (runStatus != null) { const isAwaitingRecovery = VALID_ER_RUN_STATUSES.includes(runStatus) && - runStatus !== RUN_STATUS_STOP_REQUESTED && - failedCommand != null // Prevents one render cycle of an unknown failed command. + runStatus !== RUN_STATUS_STOP_REQUESTED if (isAwaitingRecovery) { setIsERActive(isValidERStatus(runStatus, true)) @@ -96,10 +111,14 @@ export function useErrorRecoveryFlows( } }, [runStatus, failedCommand]) - return { - isERActive, - failedCommand, - } + // Gate ER rendering on data derived from key network requests. + return isERActive && failedCommand != null && runLwDefsByUri != null + ? { + isERActive: true, + failedCommand, + runLwDefsByUri, + } + : { isERActive: false, failedCommand, runLwDefsByUri } } export interface ErrorRecoveryFlowsProps { @@ -109,14 +128,20 @@ export interface ErrorRecoveryFlowsProps { * information derived from the failed command from the run record even if there is no matching command in protocol analysis. * Using a failed command that is not matched to a protocol analysis command is unsafe in most circumstances (ie, in * non-generic recovery flows. Prefer using failedCommandBySource in most circumstances. */ - unvalidatedFailedCommand: FailedCommand | null + unvalidatedFailedCommand: UseErrorRecoveryActiveResult['failedCommand'] + runLwDefsByUri: UseErrorRecoveryActiveResult['runLwDefsByUri'] protocolAnalysis: CompletedProtocolAnalysis | null } export function ErrorRecoveryFlows( props: ErrorRecoveryFlowsProps ): JSX.Element | null { - const { protocolAnalysis, runStatus, unvalidatedFailedCommand, runId } = props + const { + protocolAnalysis, + runStatus, + unvalidatedFailedCommand, + runLwDefsByUri, + } = props const failedCommandBySource = useRetainedFailedCommandBySource( unvalidatedFailedCommand, @@ -128,11 +153,7 @@ export function ErrorRecoveryFlows( const robotType = protocolAnalysis?.robotType ?? OT2_ROBOT_TYPE const robotName = useHost()?.robotName ?? 'robot' - const labwareDefinitionsByUri = useRunLoadedLabwareDefinitionsByUri(runId) - const allRunDefs = - labwareDefinitionsByUri != null - ? Object.values(labwareDefinitionsByUri) - : [] + const allRunDefs = runLwDefsByUri != null ? Object.values(runLwDefsByUri) : [] const { showTakeover, @@ -150,7 +171,7 @@ export function ErrorRecoveryFlows( isActiveUser, failedCommand: failedCommandBySource, allRunDefs, - labwareDefinitionsByUri, + runLwDefsByUri, }) const renderWizard = diff --git a/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx b/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx index 33d6f79e9a7..cbb45387a5a 100644 --- a/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx +++ b/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx @@ -161,6 +161,7 @@ describe('RunningProtocol', () => { vi.mocked(useErrorRecoveryFlows).mockReturnValue({ isERActive: false, failedCommand: {} as any, + runLwDefsByUri: {} as any, }) vi.mocked(useInterventionModal).mockReturnValue({ showModal: false, @@ -224,6 +225,7 @@ describe('RunningProtocol', () => { vi.mocked(useErrorRecoveryFlows).mockReturnValue({ isERActive: true, failedCommand: {} as any, + runLwDefsByUri: {} as any, }) render(`/runs/${RUN_ID}/run`) screen.getByText('MOCK ERROR RECOVERY') diff --git a/app/src/pages/ODD/RunningProtocol/index.tsx b/app/src/pages/ODD/RunningProtocol/index.tsx index 33d9d515930..eee50894ebb 100644 --- a/app/src/pages/ODD/RunningProtocol/index.tsx +++ b/app/src/pages/ODD/RunningProtocol/index.tsx @@ -122,7 +122,10 @@ export function RunningProtocol(): JSX.Element { const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) const robotAnalyticsData = useRobotAnalyticsData(robotName) const robotType = useRobotType(robotName) - const { isERActive, failedCommand } = useErrorRecoveryFlows(runId, runStatus) + const { isERActive, failedCommand, runLwDefsByUri } = useErrorRecoveryFlows( + runId, + runStatus + ) const { showModal: showIntervention, modalProps: interventionProps, @@ -169,6 +172,7 @@ export function RunningProtocol(): JSX.Element { runStatus={runStatus} runId={runId} unvalidatedFailedCommand={failedCommand} + runLwDefsByUri={runLwDefsByUri} protocolAnalysis={robotSideAnalysis} /> ) : null} diff --git a/app/src/resources/runs/useRunLoadedLabwareDefinitionsByUri.ts b/app/src/resources/runs/useRunLoadedLabwareDefinitionsByUri.ts index d7ffb086d10..9a785cb99df 100644 --- a/app/src/resources/runs/useRunLoadedLabwareDefinitionsByUri.ts +++ b/app/src/resources/runs/useRunLoadedLabwareDefinitionsByUri.ts @@ -18,23 +18,29 @@ export type RunLoadedLabwareDefinitionsByUri = Record< // Returns a record of labware definitions keyed by URI for the labware that // has been loaded with a "loadLabware" command. Errors if the run is not the current run. +// Returns null if the network request is pending. export function useRunLoadedLabwareDefinitionsByUri( runId: string | null, options: UseQueryOptions = {}, hostOverride?: HostConfig -): RunLoadedLabwareDefinitionsByUri { +): RunLoadedLabwareDefinitionsByUri | null { const { data } = useRunLoadedLabwareDefinitions(runId, options, hostOverride) return useMemo(() => { const result: Record = {} - // @ts-expect-error TODO(jh, 10-12-24): Update the app's typing to support LabwareDefinition3. - data?.data.forEach((def: LabwareDefinition2) => { - if ('schemaVersion' in def) { - const lwUri = getLabwareDefURI(def) - result[lwUri] = def - } - }) - return result + if (data == null) { + return null + } else { + // @ts-expect-error TODO(jh, 10-12-24): Update the app's typing to support LabwareDefinition3. + data?.data.forEach((def: LabwareDefinition2) => { + if ('schemaVersion' in def) { + const lwUri = getLabwareDefURI(def) + result[lwUri] = def + } + }) + + return result + } }, [data]) }