13 Feb 2018

How to use React in a Forge Viewer DockingPanel

    The question came up last week from a colleague: How to use a React component inside a Viewer DockingPanel element?

    DockingPanels are typically created dynamically by a Viewer extension loaded at runtime, a panel is creating a DOM node to host its content using the DOM API whereas React is using Virtual DOM. In order to use a React component you need to instantiate it somewhere within your React application tree, hopefully there is a workaround to bind a component to a DOM node dynamically: ReactDOM.render.

    This is the trick that I use here: creating a DOM node inside an Autodesk.Viewing.UI.DockingPanel and using ReactDOM.render to inject our custom React component inside that panel.

    Let's first write a basic React component that we will display in our Panel, this could be any existing React component that you have, as long as it doesn't depend on the state of your app:


import React from 'react'

export default class ReactPanelContent extends React.Component {

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  constructor (props) {

    super (props)

    this.update = this.update.bind(this)

    this.state = {
      date: ''
    }
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  componentDidMount () {

    this.intervalId = window.setInterval(this.update, 1000)
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  componentWillUnmount () {

    window.clearInterval(this.intervalId)
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  update () {

    this.setState({
      date: new Date().toLocaleString()
    })
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  render () {

    const {date} = this.state

    return (
      <div className="react-content">
        Rendered by React!
        <br/>
        { date }
      </div>
    )
  } 
}

    Then we write a simple wrapper class that extends Autodesk.Viewing.UI.DockingPanel and binds our React component to the content of the panel:

import ReactPanelContent from './ReactPanelContent'
import ReactDOM from 'react-dom'
import './ReactPanel.scss'
import React from 'react'

export default class ReactPanel extends Autodesk.Viewing.UI.DockingPanel {

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  constructor (viewer, options) {

    super (viewer.container, options.id, options.title, {
      addFooter: false,
      viewer
    })

    this.container.classList.add('react-docking-panel')

    this.DOMContent = document.createElement('div')

    this.DOMContent.className = 'content'

    this.container.appendChild(
      this.DOMContent) 
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  initialize () {

    super.initialize()

    this.viewer = this.options.viewer

    this.footer = this.createFooter()

    this.container.appendChild(
        this.footer)
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  setVisible (show) {

    super.setVisible(show)

    if (show) {

      this.reactNode = ReactDOM.render(
        <ReactPanelContent/>,
        this.DOMContent)

    } else if (this.reactNode) {

      ReactDOM.unmountComponentAtNode(
        this.DOMContent)

      this.reactNode = null  
    }
  }
}

    You can conveniently style the panel using css (or sass, scss, less whatever your build system is configured to support) rather than implementing the styling through JavaScript. Here is how my styles look like for this panel:

.react-docking-panel {
  height: 208px;
  width: 238px;
  resize: auto;
  left: 10px;
  top: 10px;

  .content {
    height: calc(100% - 35px);

    .react-content {
      margin: 10px;
    }
  }
}

    Finally I create a simple extension that instantiate our panel and display it upon loading:

/////////////////////////////////////////////////////////////////
// ReactPanel Viewer Extension
// By Philippe Leefsma, Autodesk Inc, Feb 2018
//
/////////////////////////////////////////////////////////////////
import ReactPanel from './ReactPanel'

class ReactPanelExtension extends Autodesk.Viewing.Extension {

  /////////////////////////////////////////////////////////
  // Class constructor
  //
  /////////////////////////////////////////////////////////
  constructor (viewer, options) {

    super (viewer, options)

    options.loader.show(false)

    this.panel = new ReactPanel(viewer, {
      id: 'react-panel-id',
      title: 'React Panel'
    })
  }

  /////////////////////////////////////////////////////////
  // Load callback
  //
  /////////////////////////////////////////////////////////
  load () {

    console.log('Viewing.Extension.ReactPanel loaded')

    this.panel.setVisible(true)

    return true
  }

  /////////////////////////////////////////////////////////
  // Extension Id
  //
  /////////////////////////////////////////////////////////
  static get ExtensionId () {

    return 'Viewing.Extension.ReactPanel'
  }

  /////////////////////////////////////////////////////////
  // Unload callback
  //
  /////////////////////////////////////////////////////////
  unload () {

    console.log('Viewing.Extension.ReactPanel unloaded')

    return true
  }
}

Autodesk.Viewing.theExtensionManager.registerExtension (
  ReactPanelExtension.ExtensionId,
  ReactPanelExtension)

export default 'Viewing.Extension.ReactPanel'

    That's it! You can find the live demo here and the full source code at Viewing.Extension.ReactPanel

React Panel

    I am assuming here that you already have a web application that supports ES6 + JSX transpilling... if it's not the case the best way to get started with it is to take a look at create-react-app.

Related Article