dojo dragon main logo

Configuring widgets through properties

The concept of properties passed to nodes in the VDOM is a central pillar of Dojo. Node properties serve as the main conduit for propagating state through an application, passing it down from parent to child widgets, as well as back up through the hierarchy via event handlers. They also serve as the main API for consumers to interact with a widget, where parent widgets pass properties to configure both their own DOM representation (when returning VNodes) as well as any child widgets they may manage (when returning WNodes).

VNodes accept properties of type VNodeProperties, and WNodes accept a minimum of WidgetProperties. Widget authors usually define their own properties interface that clients are then required to pass in.

VDOM node keys

WidgetProperties is very simple, containing a single optional property of key - which is also common across VNodeProperties.

Specifying a key is required when widgets begin to output several elements of the same type at the same level in the VDOM. For example, a list widget managing several list items would need to specify a key for each individual item in the list.

Dojo uses a virtual node's key to uniquely identify a specific instance when re-rendering affected portions of the VDOM. Without a key to differentiate multiple nodes of the same type at the same level in the VDOM, Dojo cannot accurately determine which subset of nodes may be affected by an invalidating change.

Note: Virtual node keys should be consistent across multiple render function invocations. Generating different keys for what should be the same output node within every render call is considered an anti-pattern in Dojo application development, and should be avoided.

Defining widget keys

Traditionally the widget's key property is used by the Dojo rendering engine to uniquely identify and track widgets across renders. However, updating the key property is also an effective way to guarantee that during the next render Dojo's rendering engine will recreate the widget instead of reusing the previous instance. When recreating the widget all previous state will get reset. This behavior is useful when working with widgets that manage logic based on the value of a widget property.

Dojo provides a mechanism for widget authors to associate a widget property to the widget's identity by using the .key() chained method from the create() factory.

import { create } from '@dojo/framework/core/vdom';

interface MyWidgetProperties {
	id: string;
}

const factory = create()
	.properties<MyWidgetProperties>()
	.key('id');

Using this factory Dojo will recreate the widget instance if the id property changes. This powerful feature provides widget authors assurance their widget will get recreated when the defined property changes, therefore not having to deal with complicated logic to refresh data based on the property.

import { create } from '@dojo/framework/core/vdom';
import icache from '@dojo/framework/core/middleware/icache';

interface MyWidgetProperties {
	id: string;
}

const factory = create({ icache })
	.properties<MyWidgetProperties>()
	.key('id');

const MyWidget = factory(function MyWidget({ properties, middleware: { icache } }) {
	const { id } = properties();
	const data = icache.getOrSet('data', async () => {
		const response = await fetch(`https://my-api/items/${id}`);
		const json = await response.json();
		return json.data;
	});

	if (!data) {
		return <div>Loading Data...</div>;
	}

	return (
		<div>
			<ul>{data.map((item) => <li>{item}</li>)}</ul>
		</div>
	);
});

This example demonstrates fetching data based on the id property. Without using .key('id), the widget would need to manage scenarios where the id property changes. This would include logic to determine if the property has actually changed, re-fetch the relevant data and also show the loading message. Using .key('id') guarantees that when the id property changes the widget will get recreated and the state reset, and the widget shows the "Loading Data..." message and fetches data based on the updated id.

Configuring VNodes

VNodeProperties contains many fields that act as the primary API to interact with concrete elements in the DOM. Many of these properties mirror those available on HTMLElement, including specifying various oneventname event handlers.

Application of these properties is considered unidirectional, in that Dojo applies the given set to concrete DOM elements but does not synchronize any further changes made to the corresponding DOM attributes back into the given VNodeProperties. Any such changes should instead be propagated back into the Dojo application via event handlers. When an event handler is invoked, the application can process any change in state required for the event, update its view of the corresponding VNodeProperties when outputting its VDOM structure for rendering, and then let Dojo's Renderer synchronize any relevant updates with the DOM.

Changing properties and diff detection

Dojo uses virtual node properties to determine if a given node has been updated and therefore requires re-rendering. Specifically, it uses a difference detection strategy to compare sets of properties from the previous and current render frames. If a difference is detected in the latest set of properties that a node receives, that node is invalidated and gets re-rendered in the next paint cycle.

Note that function properties are ignored during property diff detection as it is a common pattern to instantiate a new function on every render. Consider the following example in which the child widget, ChildWidget, receives a new onClick function on every render.

export const ParentWidget(function ParentWidget() {
  return <ChildWidget onClick={() => {
      console.log('child widget clicked.');
  }} />
});

If functions were checked during diff detection, this would cause ChildWidget to re-render every time ParentWidget rendered.

Be aware: Property change detection is managed internally by the framework, and is dependent on the declarative structure of widgets' VDOM output from their render functions. Attempting to keep references to properties and modifying them outside of the usual widget render cycle is considered an anti-pattern in Dojo application development, and should be avoided.