Do not call externalDataManager.setExternalData while rendering

The ExternalDataManager is deprecated, please use the addExternalValue API.

This error occurs if the externalDataManager.setExternalData method is called from within the code of a custom object or widget. Updating external data while views are being rendered can lead to unexpected behavior, which is why we prevent this by throwing this error.

To solve this error, consider why you are calling setExternalData. If the result of the data being set represents a default value, the object or widget could treat the absence of the value as if that default value was set. If the external value is the result of an expensive computation, make sure that any call to setExternalData happens asynchronously so it will never coincide with rendering.

JavaScript

import externalDataManager from 'fontoxml-templated-views/src/externalDataManager.js';

// Wrong: do not modify external data while rendering
function renderCustomWidget(sourceNode, renderer) {
	let externalValue = sourceNode.getExternalValue('my-external-value');
	if (externalValue === undefined) {
		// Value is missing, initialize
		externalValue = 'initial-value';
		externalDataManager.setExternalData('my-external-value', externalValue);
	}
	return ['span', externalValue];
}

// Solution 1: treat undefined as the initial value
function renderCustomWidget(sourceNode, renderer) {
	let externalValue = sourceNode.getExternalValue('my-external-value');
	if (externalValue === undefined) {
		// Value is missing, use initial value
		externalValue = 'initial-value';
	}
	return ['span', externalValue];
}

// Solution 2: ensure set happens elsewhere, asynchronously
function renderCustomWidget(sourceNode, renderer) {
	let externalValue = sourceNode.getExternalValue('my-external-value');
	if (externalValue === undefined) {
		// Value is missing, use fallback value
		externalValue = 'fallback-value';
	}
	return ['span', externalValue];
}
// Somewhere else:
computeExternalValue().then(value => {
	externalDataManager.setExternalData('my-external-value', value);
});

// Solution 3 or temporary work-around: force the call to be async using setTimeout
// Note that the other solutions are preferred, as this approach will cause an unnecessary view update.
function renderCustomWidget(sourceNode, renderer) {
	let externalValue = sourceNode.getExternalValue('my-external-value');
	if (externalValue === undefined) {
		// Value is missing, initialize
		externalValue = 'fallback-value';
		setTimeout(() => {
			externalDataManager.setExternalData('my-external-value', 'initial-value');
		}, 0);
	}
	return ['span', externalValue];
}

The following example shows a typical instance of code causing this problem, as well as several potential solutions:

Refer to the guide on custom XPath/XQuery invalidation for more information on how to expose external data to XPath using addExternalValue.

If your application uses private API to invalidate a custom XPath function, you may see a different error message: "Can not invalidate external dependency while computing."

The cause is the same as the error above, and similar solutions can be applied.