Model update issues after 3.x upgrade

Hi Team,

We are currently using the following code to dynamically update the palette with new nodes. This approach used to work as expected in the older GoJS version (2.3.5):

palette.model.startTransaction('updatePaletteJSON');
palette.model.nodeDataArray = paletteJSON;
palette.model.commitTransaction('updatePaletteJSON');

However, after upgrading to GoJS version 3.0.20, the above code no longer replaces the existing nodes in the palette. Instead, it appends the new nodes to the end of the existing ones.

Our use case requires a complete replacement of the existing palette nodes with the new ones, not an append.

We also tried the following workaround:

palette.model = go.Model.fromJson({ nodeDataArray: paletteJSON });

While this does replace the nodes, it also clears the templateMap, which we want to retain as it’s reused elsewhere and we prefer not to repopulate it every time.

Could you please suggest an alternative approach that allows us to fully replace the palette nodes while preserving the existing templateMap?

Thanks,
Tejesh

Check the value of paletteJSON to make sure it is an Array with exactly as many node data objects as you are expecting.

Setting the Diagram.model property of any kind of diagram, including a Palette, definitely does not modify any templates. I cannot explain that behavior in your app.

Hi @walter,

Yes, paletteJSON is an array. I’ve updated my code so that instead of modifying palette.model.nodeDataArray directly, I now create a new model, set its nodeDataArray to the new paletteJSON, and then assign this model to the palette diagram. This approach successfully replaces the nodes in the palette instead of appending them.

Code that appends the nodes:

// If the palette diagram is already rendered, update the palette model with the new paletteJSON.
if (palette) {
	palette.model.startTransaction('updatePaletteJSON');
	palette.model.nodeDataArray = paletteJSON;
	palette.model.commitTransaction('updatePaletteJSON');
} else {
	const paletteModel = new go.GraphLinksModel();

	paletteModel.nodeKeyProperty = 'key';
	paletteModel.nodeDataArray = paletteJSON;
	paletteModel.nodeCategoryProperty = 'templateId';

	// Main Palette model rendering
	palette = new go.Palette(paletteNode, {
		model: paletteModel,
	});
}

Code that replaces the JSON correctly:

const paletteModel = new go.GraphLinksModel();

paletteModel.nodeKeyProperty = 'key';
paletteModel.nodeDataArray = paletteJSON;
paletteModel.nodeCategoryProperty = 'templateId';

// If the palette diagram is already rendered, update the palette model with the new paletteJSON.
if (palette) {
	palette.model = paletteModel;
} else {
	// Main Palette model rendering
	palette = new go.Palette(paletteNode, {
		model: paletteModel,
	});
}

Can we please know why there is a difference in behavior when updating the model between these two approaches?
In the first case, modifying nodeDataArray directly seems to append the nodes, while in the second case, assigning a new model replaces them entirely. It would be helpful to understand what causes this difference under the hood.

Here’s a complete stand-alone example that demonstrates both approaches. You might want to reload the page before clicking the other button, although the correct behavior happens either way.

It does not exhibit the behavior your code does. Might there be some other differences in how you configured your diagram(s)?

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2025 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
  <button id="myTestButton">Replace nodeDataArray</button>
  <button id="myTestButton2">Replace model</button>

  <script src="https://cdn.jsdelivr.net/npm/gojs/release/go-debug.js"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv");

myDiagram.nodeTemplate =
  new go.Node("Auto")
    .add(
      new go.Shape({ fill: "white", stroke: "green" })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })
        .bind("text")
    );

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange" },
  { key: 3, text: "Gamma", color: "lightgreen" },
  { key: 4, text: "Delta", color: "pink" }
],
[
  { from: 1, to: 2 },
  { from: 1, to: 3 },
  { from: 2, to: 2 },
  { from: 3, to: 4 },
  { from: 4, to: 1 }
]);

document.getElementById("myTestButton").addEventListener("click", e => {
  myDiagram.model.commit(m => {
    m.nodeDataArray =
      [
        { key: 1, text: "Hello" },
        { key: 2, text: "World!" }
      ];
    m.linkDataArray =
      [
        { from: 1, to: 2 }
      ];
  });
});

document.getElementById("myTestButton2").addEventListener("click", e => {
  myDiagram.model = new go.GraphLinksModel(
    [
      { key: 1, text: "Hello" },
      { key: 2, text: "World!" }
    ],
    [
      { from: 1, to: 2 }
    ]);
});
  </script>
</body>
</html>

I tried using the approach from the above examples in our diagram, like this:

palette.model.commit(m => {
    m.nodeDataArray = paletteJSON;
});

However, this still appends the nodes instead of replacing them. The alternative method, where the GraphLinksModel is re-initialized, works as expected.

As far as I can tell, there isn’t much difference between our diagram configuration and the example above.

Are you using React and gojs-react?

What Diagram and Model listeners have you defined? Have you overridden any Diagram or Model methods?

We are using framework similar to react Walter.

below are the override methods or properties for palette diagram that we have

maxSelectionCount: 1,
        allowDelete: false,
        'draggingTool.doActivate': function() {
            go.DraggingTool.prototype.doActivate.call(this);
            this.diagram.findLayer('Tool').opacity = 0.5;
        },
        'draggingTool.doDeactivate': function() {
            go.DraggingTool.prototype.doDeactivate.call(this);
            this.diagram.findLayer('Tool').opacity = 1;
        },
        'contextMenuTool.standardMouseClick': function() {
            return false;
        }

Those overrides like fine – not a problem.

I cannot explain the odd behavior.