Ref not set when using ReactDiagram in functional component

The gojs-react-basic example is built using the legacy Component pattern. I am trying to use the ReactDiagram component in a React functional component instead. I am running into problems with the ref variable not getting set when the ReactDiagram is mounted.

My component looks like below (non-relevant stuff removed).

The issue is that it seems to take some time before <ReactDiagram> sets the ref. When the component is mounted the diagramRef.current is undefined, but later, if some external update causes the component to re-render, then it gets set correctly.

So my event listeners will not get set in the beginning, which is rather annoying…

const NetworkEditor = ( { cells } ) => {
...
    let diagramRef = React.useRef()
...
    console.log( 'REF', diagramRef )

    useEffect( () => {
            if( !diagramRef.current ) return
            const diagram = diagramRef.current.getDiagram()
            if( diagram instanceof go.Diagram ) {
                console.log( 'Adding event listeners....' )
                diagram.addDiagramListener( 'ChangedSelection', handleDiagramEvent )
                diagram.addDiagramListener( 'ObjectDoubleClicked', handleDiagramEvent )
            }
        },
        [ diagramRef, diagramRef.current ] )

    const nodeDataArray = cells
        .filter( c => c.type !== 'link' )
        .map( c => ( {
            key: c.id,
            text: c.attrs[ '.label' ].text,
            fill: '#e7e7e7',
            stroke: c.attrs[ '.body' ].fill,
            strokeWidth: c.attrs[ '.body' ].strokeWidth,
            loc: c.position.x + ' ' + c.position.y,
            leftArray: c.inPorts.map( portId => ( { portId } ) ),
            rightArray: c.outPorts.map( portId => ( { portId } ) ),
            ...c.cfObject
        } ) )

    const linkDataArray = cells
        .filter( c => c.type === 'link' )
        .map( c => ( {
            key: c.z,
            from: c.source.id,
            to: c.target.id,
            fromPort: c.source.port,
            toPort: c.target.port
        } ) )

    return (
        <div>

            <ReactDiagram
                ref={diagramRef}
                initDiagram={initDiagram}
                divClassName='diagram-component'
                nodeDataArray={nodeDataArray}
                linkDataArray={linkDataArray}
                linkKeyProperty="key"
                onModelChange={console.log}
            />

        </div>
    )
}

Does this help?

I did read that before posting as a good citizen should :-)
No I am afraid not, that example does not use the ref functionality.

Ok, I found some various ways to solve this, but the most proper way seems to be to follow the tip at https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node.

The start of the component above then becomes:

const NetworkEditor = ( { cells } ) => {
...
    const diagramRef = useCallback( element => {
            if( element === null ) return
            const diagram = element.getDiagram()
            if( diagram instanceof go.Diagram ) {
                console.log( 'Adding event listeners....' )
                diagram.addDiagramListener( 'ChangedSelection', handleDiagramEvent )
                diagram.addDiagramListener( 'ObjectDoubleClicked', handleDiagramEvent )
            }
        }, [] )
...

The trick seems to be that useRef does not pickup changes to child elements in the DOM, so I guess what is happening is that does a lot of stuff that gets unnoticed.

The examples change from useRef to useCallback is explained this way:

We didn’t choose useRef in this example because an object ref doesn’t notify us about changes to the current ref value. Using a callback ref ensures that even if a child component displays the measured node later (e.g. in response to a click), we still get notified about it in the parent component and can update the measurements.

Not exactly the same thing, but when using useCallback in the examples fashion my event listeners get set as they should after the diagram is mounted.