Unable to return HTMLDivElement in typescript component module

I am attempting to show a WebGL renderer in a perspective component, and have been working with a test component forked off the perspective example “image” component.

I think I have made a bit of progress and the Typescript itself compiles, but when I try to return either my domElement or the container that I put it in, I get a react error saying:
Objects are not valid as a React child (found: [object HTMLDivElement]).rgs[]

I am assuming this is because I need to be passing it back some type of html, as thats what it looks like in any of the examples I can find, but I would like to pass back the already created element, any ideas?

I am 100% new to Javascript and Typescript so I appreciate your patience if the answer is painfully obvious

Here is a copy of the module, and I greatly appreciate any help.

import * as React from 'react';
import { Component, ComponentMeta, ComponentProps, SizeObject } from '@inductiveautomation/perspective-client';
import { observer } from 'mobx-react';
import * as THREE from '../include/three.module.js';
import { OrbitControls } from '../include/OrbitControls.js';
import { GLTFLoader } from '../include/GLTFLoader.js';

export const COMPONENT_TYPE = "ThreeD.display.Viewer";

export interface ViewerProps {
    color: string;  

export class Viewer extends Component<ComponentProps, any> {
    render() {
        const container = document.createElement( 'div' );

        const camera: any  = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 100 );
        const scene: any = new THREE.Scene();
        const renderer: any  = new THREE.WebGLRenderer({ antialias: true });
        const controls: any  = new OrbitControls( camera, renderer.domElement );
        const loader: any = new GLTFLoader();


        function init() {
            camera.position.set( - 25, 15, 15);
            let ambientLight = new THREE.AmbientLight( 0x404040 );
            let directionalLight1: any  = new THREE.DirectionalLight( 0xC0C090 );
            let directionalLight2: any  = new THREE.DirectionalLight( 0xC0C090 );
            directionalLight1.position.set( - 100, - 50, 100 );
            directionalLight2.position.set( 100, 50, - 100 );
            scene.add( directionalLight1 );
            scene.add( directionalLight2 );
            scene.add( ambientLight );
            loader.setPath( 'models/Trapezoid/' );
            loader.load( 'Trapezoid.glb', function ( gltf: { scene: any } ) {
                scene.add( gltf.scene );
            } );
            renderer.setPixelRatio( window.devicePixelRatio );
            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 0.8;
            renderer.outputEncoding = THREE.sRGBEncoding;
            container.appendChild( renderer.domElement );
            const pmremGenerator = new THREE.PMREMGenerator( renderer );
            controls.addEventListener( 'change', render ); 
            controls.minDistance = 2;
            controls.maxDistance = 100;
            controls.target.set( 0, 0, - 0.2 );
            window.addEventListener( 'resize', onWindowResize, false );

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            renderer.setSize( window.innerWidth, window.innerHeight );

        function render() {
            renderer.render( scene, camera );
        //This is presumably where the error is coming from

// this is the actual thing that gets registered with the component registry
export class ViewerMeta implements ComponentMeta {

    getComponentType(): string {
        return COMPONENT_TYPE;

    // the class or React Type that this component provides
    getViewClass(): React.ReactType {
        return Viewer;

    getDefaultSize(): SizeObject {
        return ({
            width: 360,
            height: 360

Disclaimer: Nothing like a React expert.

I think you want to use a ref here; see the simple example near the top of this page:

Your init and other functions should probably be top-level functions on your component.

Alternately, there are existing bindings between Three.js and React; it might be easier to wrap something like GitHub - pmndrs/react-three-fiber: 🇨🇭 A React renderer for Three.js into a Perspective component.


Thank you Paul!

Refs definitely look like what I need for this example, but I can also see where the react-three-fiber library would accomplish what I want easier.

This has been quite the learning experience, considering I started fooling with JavaScript like 2 days ago hahaha