5 Use Oracle JET Components and Data Providers
Review the following recommendations to make effective use of Oracle JET components and associated APIs, such as data providers, so that the app that you develop is performant and provides an optimal user experience.
Access Subproperties of Oracle JET Component Properties
JSX does not support the dot notation that allows you to access a subproperty of a component property. You cannot, for example, use the following syntax to access the max
or count-by
subproperties of the Input Text element’s length
property.
<oj-input-text
length.max={3}
length.count-by="codeUnit"
</oj-input-text>
To access these subproperties using JSX, first access the element’s top-level property and set values for the subproperties you want to specify. For example, for the countBy
and max
subproperties of the Oracle JET oj-input-text
element’s length
property, import the component props that the Input Text element uses. Then define a length
object, based on the type InputTextProps["length"]
, and assign it values for the countBy
and max
subproperties. Finally, pass the length
object to the oj-input-text
element as the value of its length
property.
import {ComponentProps } from "preact";
type InputTextProps = ComponentProps<"oj-input-text">;
const length: InputTextProps["length"] = {
countBy: "codeUnit",
max: 3
};
function Parent() {
return (
<oj-input-text length={ length } />
)
}
Mutate Properties on Oracle JET Custom Element Events
Unlike typical Preact components, mutating properties on Oracle JET custom elements invokes property-changed callbacks. As a result, you can end up with unexpected behavior, such as infinite loops, if you:
- Have a property-changed callback
- The property-changed callback triggers a state update
- The state update creates a new property value (for example, copies values into a new array)
- The new property value is routed back into the same property
Typically, Preact (and React components) invoke callbacks in response
to user interaction, and not in response to a new property value being
passed in by the parent component. You can simulate the Preact
componnent-type behavior when you use Oracle JET custom elements if you
check the value of the event.detail.updatedFrom
field to
determine if the property change is due to user interaction
(internal
) instead of your app programmatically
mutating the property value (external
). For example, the
following event callback is only invoked in response to user interaction:
. . .
const onSelection = (event) => {
if (event.detail.updatedFrom === 'internal') {
const selectedValues = event.detail.value;
setSelectedOptions([selectedValues]);
}
};
. . .
Avoid Repeated Data Provider Creation
Do not re-create a data provider each time that a VComponent renders.
For example, do not do the following in a VComponent using an
oj-list-view
component, as you re-create the
MutableArrayDataProvider
instance each time the
VComponent renders:
<oj-list-view
data={new MutableArrayDataProvider....}
. . . >
</oj-list-view>
Instead, consider using Preact's useMemo hook to ensure that a data
provider instance is re-created only if the data in the data provider
actually changes. The following example demonstrates how you include the
useMemo hook to ensure that the data provider providing data to an
oj-list-view
component is not re-created, even if
the VComponent that includes this code re-renders.
import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider");
import { Task, renderTask } from "./data/task-data";
import { useMemo } from "preact/hooks";
import "ojs/ojlistview";
import { ojListView } from "ojs/ojlistview";
type Props = {
tasks: Array<Task>;
}
export function ListViewMemo({ tasks }: Props) {
const dataProvider = useMemo(() => {
return new MutableArrayDataProvider(
tasks, {
keyAttributes: "taskId"
}
);
},
[ tasks ]
);
return (
<oj-list-view data={dataProvider} class="demo-list-view">
<template slot="itemTemplate" render={renderTask} />
</oj-list-view>
)
}
The reason for this recommendation is that a VComponent can re-render for any number of reasons, but as long as the data provided by the data provider does not change, there is no need to incur the cost of re-creating the data provider instance. Re-creating a data provider can affect your app's performance, due to unnecessary rendering, and usability as collection components may flash and lose scroll position as they re-render.
Avoid Data Provider Re-creation When Data Changes
Previously, we described how the useMemo hook avoids re-creating a data provider unless data changes. When data does change, we still end up re-creating the data provider instance, and this can result in collection components, such as list view, re-rendering all data displayed by the component and losing scroll position.
Here, we'll try to illustrate how you can opitimize the user experience by
implementing fine-grained updates and maintaining scroll position for collection components,
even if the data referenced by the data provider changes. To accomplish this, the data
provider uses a MutableArrayDataProvider
instance. With a
MutableArrayDataProvider
instance, you can mutate an existing instance by
setting a new array value into the MutableArrayDataProvider
's data field. The
collection component is notified of the specific change (create, update, or delete) that
occurs, which allows it to make a fine-grained update and maintain scroll position.
In the following example, an app displays a list of tasks in a list view
component and a Done button that allows a user to remove a completed task. These components
render in the Content component of a virtual DOM app created with the basic template
(appRootDir/src/components/content/index.tsx
).

To implement fine-grained updates and maintain scroll position for the
oj-list-view
component, we store the list view's
MutableArrayDataProvider
in local state using Preact’s useState hook and
never re-create it, and we also use Preact’s useEffect hook to update the data field of the
MutableArrayDataProvider
when a change to the list of tasks is detected.
The user experience is that a click on Done for an item in the tasks list removes the item
(with removal animation). No refresh of the oj-list-view
component or scroll
position loss occurs.
import "ojs/ojlistview";
import { ojListView } from "ojs/ojlistview";
import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider");
import { Task, renderTask } from "./data/task-data";
import { useState, useEffect } from "preact/hooks";
type Props = {
tasks: Array<Task>;
}
export function ListViewState({ tasks, onTaskCompleted }: Props) {
const [ dataProvider ] = useState(() => {
return new MutableArrayDataProvider(
tasks, {
keyAttributes: "taskId"
}
);
});
useEffect(() => {
dataProvider.data = tasks;
}, [ tasks ]);
return (
<oj-list-view data={dataProvider} class="demo-list-view">
<template slot="itemTemplate" render={renderTaskWithCompletedCallback} />
</oj-list-view>
)
}
Use Oracle JET Popup and Dialog Components
To use Oracle JET's popup or dialog components (popup content) in a
VComponent or a virtual DOM app, create a reference to the popup content. We recommend too that
you place the popup content in its own div
element so that it works when used
with Preact's reconciliation logic.
Currently, to launch popup content from within JSX, you must create a reference
to the custom element and manually call open()
, as in the following example
for a VComponent class component that uses Preact's createRef
function:
import { customElement, ExtendGlobalProps } from "ojs/ojvcomponent";
import { h, Component, ComponentChild, createRef } from "preact";
import "ojs/ojdialog";
import "oj-c/button";
. . .
@customElement("popup-launching-component")
export class PopupLaunchingComponent extends Component<ExtendGlobalProps<Props>> {
private dialogRef = createRef();
showDialog = () => {
this.dialogRef.current?.open();
console.log("open dialog");
}
render(): ComponentChild {
return <div>
<oj-c-button onojAction={this.showDialog} label="Show Dialog"></oj-c-button>
<div>
<oj-dialog ref={this.dialogRef} cancelBehavior="icon"
modality="modeless" dialogTitle="Dialog Title">
<div>Hello, World!</div>
</oj-dialog>
</div>
</div>
}
}
As a side effect of the open()
call, Oracle JET relocates the
popup content DOM to an Oracle JET-managed popup container, outside of the popup-launching
component. This works and the user can see and interact with the popup.
If, while the popup is open, the popup-launching component is re-rendered by, for
example, a state change, Preact's reconciliation logic detects that the popup content element
is no longer in its original location and will reparent the still-open popup content back to
its original parent. This interferes with Oracle JET's popup service, and unfortunately leads
to non-functional popup content. To avoid this issue, we recommend that you ensure that the
popup content is the only child of its parent element. In the following functional component
example, we illustrate one way to accomplish this by placing the oj-dialog
component within its own div
element.
Note:
In the following example we use Preact'suseRef
hook to get the reference to the DOM node inside the functional
component. Use Preact's createRef
function to get the reference to the popup
content DOM node in class-based components.
import { h } from "preact";
import { useRef } from "preact/hooks";
import "ojs/ojdialog";
import "oj-c/button";
import { CButtonElement } from "oj-c/button";
import { DialogElement } from "ojs/ojdialog";
export function Content() {
const dialogRef = useRef<DialogElement>(null);
const onSubmit = (event: CButtonElement.ojAction) => {
event.preventDefault();
dialogRef.current?.open();
console.log("open dialog");
};
const close = () => {
dialogRef.current?.close();
console.log("close dialog");
};
return <div class="oj-web-applayout-max-width oj-web-applayout-content">
<oj-c-button onojAction={onSubmit} disabled={false} label="Open dialog">
</oj-c-button>
<div>
<oj-dialog ref={dialogRef} dialogTitle="Dialog Title" cancelBehavior="icon">
<div>Hello, World!</div>
<div slot="footer">
<oj-c-button id="okButton" onojAction={close} label="Close dialog">
</oj-c-button>
</div>
</oj-dialog>
</div>
</div>;
}