I have a react project and am trying to use the Autodesk Forge viewer. I have it sort of working but am getting a lot of errors and weird behavior that I don't understand. I'm pretty new to both Forge viewer and React so I'm sure I'm missing something simple but I don't know what it is.
The general idea of this page is that the user gets a list of locations from the DB (outside Forge) that they can click on. If they click on one to select it the system checks if there is a dwg file associated with it. If not it displays a generic div that says there is no file associated but if so it displays the dwg in the forge viewer. So the viewer itself is sometimes hidden but should always be there but will need to change the file that it's displaying sometimes.
Right now I have it so that when I click the first one it comes up and displays it correctly. However, when I click another and then back to the first one it blanks out and gives me an error in the console. Here is my forge component:
import React, { useEffect, useRef } from "react";
import styled from "styled-components";
import { BorderedZoneOuter } from "./Generic/CommonStyledComponents";
import { ForgeBackgroundService } from "../services/ForgeBackgroundService";
import { valueIsNullOrUndefined } from "../Utility";
const viewerLibraryURL =
"https://developer.api.autodesk.com/modelderivative/v2/viewers/viewer3D.min.js?v=v7.*";
const viewerStylesheetURL =
"https://developer.api.autodesk.com/modelderivative/v2/viewers/style.min.css?v=v7.*";
let viewerLibraryLoaded = false;
let viewerStyleLoaded = false;
let viewerLoading = false;
const ForgeContainer = styled(BorderedZoneOuter)`
position: relative;
flex: 1;
`;
const _backgroundService = new ForgeBackgroundService();
let viewer: Autodesk.Viewing.GuiViewer3D | undefined;
const ForgeViewer = (props: {
urn: string;
viewerReady?: (viewer: Autodesk.Viewing.GuiViewer3D) => void;
}) => {
const container: any = useRef();
useEffect(() => {
const handleStyleLoad = () => {
console.log("style loaded");
viewerStyleLoaded = true;
viewerLibraryLoaded && loadViewer();
};
const handleScriptLoad = () => {
console.log("script loaded");
viewerLibraryLoaded = true;
viewerStyleLoaded && loadViewer();
};
const loadViewer = () => {
console.log("loading viewer");
if (!viewerLoading) {
viewerLoading = true;
Autodesk.Viewing.Initializer(
{
env: "AutodeskProduction2",
api: "streamingV2",
getAccessToken: (onTokenReady) => {
if (onTokenReady) {
_backgroundService.GetToken().then((t) => {
if (valueIsNullOrUndefined(t)) {
return;
}
onTokenReady(t!.token, t!.expiresIn);
});
}
},
},
() => {
viewer = new Autodesk.Viewing.GuiViewer3D(container.current);
viewer.start();
loadModel(props.urn);
viewerLoading = false;
}
);
}
};
const loadStyleSheet = (href: string) => {
const styles = document.createElement("link");
styles.rel = "stylesheet";
styles.type = "text/css";
styles.href = href;
styles.onload = handleStyleLoad;
document.getElementsByTagName("head")[0].appendChild(styles);
};
const loadViewerScript = (href: string) => {
const script = document.createElement("script");
script.src = href;
script.async = true;
script.onload = handleScriptLoad;
document.getElementsByTagName("head")[0].appendChild(script);
};
function loadModel(urn: string): void {
console.log(urn);
console.log(viewer);
Autodesk.Viewing.Document.load(
urn,
(doc) => {
console.log(doc);
const defaultModel = doc.getRoot().getDefaultGeometry();
console.log(defaultModel);
viewer?.loadDocumentNode(doc, defaultModel)
.then((m: Autodesk.Viewing.Model) => {
console.log(m);
if (props.viewerReady) {
props.viewerReady(viewer!);
}
});
},
() => {
console.error("failed to load document");
}
);
}
if (!valueIsNullOrUndefined(viewer)) {
console.log("have viewer, loading model");
loadModel(props.urn);
} else {
console.log("no viewer, loading scripts");
loadStyleSheet(viewerStylesheetURL);
loadViewerScript(viewerLibraryURL);
}
return () => {
viewer?.finish();
};
}, [props]);
return <ForgeContainer ref={container} />;
};
export default ForgeViewer;
In the parent component that uses the viewer, here is the relevant portion of the tsx:
<MainCanvas>
<PageTitle>Create Spaces</PageTitle>
<button onClick={select}>select</button>
{valueIsNullOrUndefined(state.selectedFloor) && (
<NoBackgroundZone>
<div>You have not selected a floor</div>
<div>
Please select a floor to view the background and create spaces
</div>
</NoBackgroundZone>
)}
{!valueIsNullOrUndefined(state.selectedFloor) &&
valueIsNullOrUndefined(state.backgroundUrn) && (
<NoBackgroundZone>
<div>There is no background added for this floor</div>
<LinkButton>Add a background</LinkButton>
</NoBackgroundZone>
)}
{!valueIsNullOrUndefined(state.selectedFloor) &&
!valueIsNullOrUndefined(state.backgroundUrn) && (
<ForgeViewer urn={state.backgroundUrn!} viewerReady={viewerReady} />
)}
</MainCanvas>
The error I get is:
Uncaught TypeError: Cannot read properties of null (reading 'hasModels') Viewer3D.js:1799
at C.he.loadDocumentNode (Viewer3D.js:1799:53)
at ForgeViewer.tsx:98:1
...
The line number that it's referring to in the error is the one that starts with viewer?.loadDocumnentNode(...
If it's using the conditional access how can it be null and still throwing the error? I also have logged all three variables on that line (doc, defaultModel, and viewer) right before that call and they never show up as null...
Can anyone tell me what I'm doing wrong here? I've looked at a lot of different samples but all of them seem to deal with just getting something displayed and I can't find anything about changing the display.