Fetch data from a REST API in an Oracle JET virtual DOM app
Introduction
This tutorial shows you how to access a REST service, integrate it into your Oracle JavaScript Extension Toolkit (Oracle JET) virtual DOM app, and bind data to a list view in your user interface.
Objectives
In this tutorial, you will learn how to create instances of the RESTDataProvider
class. This class represents data available from JSON-based REST services.
Prerequisites
- A development environment set up to create Oracle JET virtual DOM apps that includes an installation of Node.js
- (Option 1) Completion of the final tutorial in the previous learning path in this series: Handle Selection Events in an Oracle JET Virtual DOM App
- (Option 2) If you haven’t completed the previous learning path in this series: the jet-virtual-dom-app-temp.zip downloaded
Task 1: Download the Starter Virtual DOM App
Skip this task if you’re continuing to work in an app that you created in the previous learning path.
-
Rename
jet-virtual-dom-app-temp.zip
asJET-Virtual-DOM-app.zip
. Extract the contents to theJET-Virtual-DOM-app
directory. -
Navigate to the
JET-Virtual-DOM-app
directory, and restore the Oracle JET virtual DOM app.npm install
The virtual DOM app is ready to use.
Task 2: Access the REST Service
Click the Apex link to view the REST data for the Activities resource endpoint.
The data contains a list of activities with various attributes.
{
"items": [
{
"id": 1,
"name": "Baseball",
"short_desc": "Equipment we carry for baseball players.",
"image": "css/images/product_images/baseball.jpg"
},
. . .
],
"hasMore": false,
"limit": 25,
"offset": 0,
"count": 4,
"links": [
{
"rel": "self",
"href": "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/"
},
. . .
]
}
Familiarize yourself with the data and the properties that the endpoint returns. You’ll need to understand these details when you create an instance of RESTDataProvider
later in this tutorial. Note, for example, how the endpoint returns an items
property that references a series of individual activities.
Task 3: Create a Data Provider to Fetch Activity Data
-
Navigate to the
JET-Virtual-DOM-app/src/components/
directory and open theParentContainer1.tsx
file in an editor. -
At the start of the
ParentContainer1.tsx
file, import theRESTDataProvider
module and delete or comment out the import statements for theMutableArrayDataProvider
module and thestore_data.json
file.We also import the
useMemo
hook that we’ll use when creating theRESTDataProvider
later.import { h } from "preact"; . . . // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); // import * as storeData from "text!./store_data.json"; import { RESTDataProvider } from "ojs/ojrestdataprovider"; import { useState, useMemo } from "preact/hooks"; . . .
-
Create a
keyattributes
variable and arestServerURLActivities
variable that references the activity key attribute and the REST endpoint that you’ll pass to theRESTDataProvider
instance that you’ll create in the next step.let keyAttributes: string = 'id'; // REST endpoint that returns Activity data const restServerURLActivities: string = 'https://apex.oracle.com/pls/apex/oraclejet/lp/activities/';
-
Create a new
activityDataProvider
variable that references theRESTDataProvider
module and delete or comment out the preexistingactivityDataProvider
variable that referenced theMutableArrayDataProvider
module.We create the new
activityDataProvider
variable within theParentContainer1
function and we wrap it within auseMemo
hook to ensure that the data provider instance is re-created only if the data in the data provider actually changes.const ParentContainer1 = () => { const activityDataProvider = useMemo(() => new RESTDataProvider<Activity["id"], Activity>({ keyAttributes: keyAttributes, url: restServerURLActivities, transforms: { fetchFirst: { request: async (options) => { const url = new URL(options.url); const { size, offset } = options.fetchParameters; url.searchParams.set("limit", String(size)); url.searchParams.set("offset", String(offset)); return new Request(url.href); }, response: async ({ body }) => { const { items, totalSize, hasMore } = body; return { data: items, totalSize, hasMore }; }, }, }, }), []) . . .
Note: The
response
function above that extracts data and other properties from the endpoint response body must return an object with adata
property. Given that the endpoint we work with returns anitems
property, we assign this latter property todata
in the response function. -
Save the
ParentContainer1.tsx
file.Your
ParentContainer1.tsx
file should look similar to ParentContainer1-a.tsx.txt. -
Navigate to the
JET-Virtual-DOM-app/src/components/Activity
directory and open theActivityContainer.tsx
file in an editor. -
At the start of the
ActivityContainer.tsx
file, import theRESTDataProvider
module and comment out or delete the import statement for theMutableArrayDataProvider
module.import { h, ComponentProps } from "preact"; . . . // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); import { RESTDataProvider } from "ojs/ojrestdataprovider"; . . .
-
In the
Props
type alias, modify the optionaldata
property to reference theRESTDataProvider
type instead of the preexisting typeMutableArrayDataProvider<Activity["id"], Activity>
.type Props = { data?: RESTDataProvider<Activity["id"], Activity>; // data?: MutableArrayDataProvider<Activity["id"], Activity>; . . . };
-
Save the
ActivityContainer.tsx
file.Your
ActivityContainer.tsx
file should look similar to ActivityContainer.tsx.txt.
Task 4: Add an Error Handler to Manage a Failure to Fetch Data
The RESTDataProvider
instance provides an error option that you can use to invoke a callback function when an attempt to fetch data fails. You’ll implement this capability for the scenario where an attempt to fetch the list of activities fails.
-
In the
ParentContainer1.tsx
file from theJET-Virtual-DOM-app/src/components/
directory, import theuseRef
hook from Preact.. . . import { RESTDataProvider } from "ojs/ojrestdataprovider"; import { useState, useMemo, useRef } from "preact/hooks"; . . .
-
In the
activityDataProvider
variable that referencesRESTDataProvider
add theerror
option and a reference to the callback function that it invokes (fetchErrorHandler
).const ParentContainer1 = () => { const activityDataProvider = useMemo(() => new RESTDataProvider<Activity["id"], Activity>({ keyAttributes: keyAttributes, url: restServerURLActivities, error: fetchErrorHandler, transforms: { . . .
-
Before the
activityDataProvider
variable, add the code forfetchErrorHandler
and the hooks (useState
anduseRef
) that we use to determine if the attempt to fetch data succeeded.. . . const ParentContainer1 = () => { const [fetchStatus, setFetchStatus] = useState(true); const fetchError = useRef<string>(); const fetchErrorHandler = (errorDetail: RESTDataProvider.FetchErrorDetail<number, Activity> | RESTDataProvider.FetchResponseErrorDetail<number, Activity>) => { setFetchStatus(false); if (errorDetail.hasOwnProperty('response')) { fetchError.current = `${(errorDetail as RESTDataProvider.FetchResponseErrorDetail<number, Activity>).response.status}`; } else { fetchError.current = (errorDetail as RESTDataProvider.FetchErrorDetail<number, Activity>).error.message; } } const activityDataProvider = new RESTDataProvider<Activity["id"], Activity>({ . . .
-
In the return statement at the end of the the
ParentContainer1.tsx
file, add a check that determines whether you display the list of activities or a message in the case where the attempt to fetch data failed.. . . return ( <div> {fetchStatus ? ( <div id="parentContainer1" class="oj-flex oj-flex-init"> <ActivityContainer data={activityDataProvider} onActivityChanged={activityChangedHandler} /> {showActivityItems() && (<ParentContainer2 activity={selectedActivity} />)} {!showActivityItems() && (<h4 class="oj-typography-subheading-sm">Select activity to view items</h4>)} </div>) : (<p>Sorry that we couldn't get your product information right now. Please contact your system administrator.</p> )} </div> ); }; export default ParentContainer1;
-
Save the
ParentContainer1.tsx
file.Your
ParentContainer1.tsx
file should look similar to ParentContainer1-b.tsx.txt.
Task 5: Create a Data Provider to Fetch Item Data
Use another RESTDataProvider
instance to fetch a subset of the data, the list of items for a particular activity. You do this by supplying a new URL that contains the selected activity ID.
-
Navigate to the
JET-Virtual-DOM-app/src/components/
directory and open theParentContainer2.tsx
file in an editor. -
At the start of the
ParentContainer2.tsx
file, import theRESTDataProvider
module and delete or comment out the import statements for theMutableArrayDataProvider
module and thestore_data.json
file. Also import theTextFilter
interface that we’ll use when we enable the filtering capability in theRESTDataProvider
instance that we create.import { h } from "preact"; . . . import { RESTDataProvider } from "ojs/ojrestdataprovider"; import { TextFilter } from "ojs/ojdataprovider"; // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); // import * as storeData from "text!./store_data.json"; . . .
-
After the
Item
type alias, create abaseServiceUrl
variable to reference the REST endpoint that you’ll pass to the instance of theRESTDataProvider
that you’ll create in the next step.type Item = { . . . }; const baseServiceUrl = "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/";
-
Create an initial instance of the
RESTDataProvider
that you’ll pass in subsequent steps to theuseState
anduseEffect
Preact hooks.const baseServiceUrl = 'https://apex.oracle.com/pls/apex/oraclejet/lp/activities/'; let INIT_DATAPROVIDER = new RESTDataProvider<ActivityItem['id'], ActivityItem>({ keyAttributes: 'id', url: baseServiceUrl, transforms: { fetchFirst: { request: null!, response: (): any => { return { data: [] }; }, }, }, });
-
Comment out or delete the pre-existing code that created a variable to read data from the
store_data.json
file and that created an initial instance of theMutableArrayDataProvider
.// const activityData = JSON.parse(storeData); // let activityItemsArray = activityData[0].items; // // Create data provider instance for the array of activity items for the selected activity // const INIT_DATAPROVIDER = new MutableArrayDataProvider<ActivityItem["id"], ActivityItem>(activityItemsArray, { // keyAttributes: "id", // })
-
In the
ParentContainer2
function, replace the existinguseEffect
hook that manages the instance ofMutableArrayDataProvider
with a new definition that creates aRESTDataProvider
for the activity items that corresponds to the selected activity ID. This new definition also includes a text filter to filter on the activity itemname
field.const ParentContainer2 = (props: Props) => { . . . useEffect(() => { setactivityItemDP( new RESTDataProvider<ActivityItem["id"], ActivityItem>({ keyAttributes: "id", capabilities: { filter: { textFilter: true, }, }, url: baseServiceUrl + "/" + props.activity?.id + "/items/", textFilterAttributes: ["name"], transforms: { fetchFirst: { request: async (options) => { const url = new URL(options.url); const { size, offset } = options.fetchParameters; url.searchParams.set("limit", String(size)); url.searchParams.set("offset", String(offset)); const filterCriterion = options.fetchParameters .filterCriterion as TextFilter<Item>; const { textFilterAttributes } = options.fetchOptions; if ( filterCriterion && filterCriterion.text && textFilterAttributes ) { const { text } = filterCriterion; textFilterAttributes.forEach((attribute) => { url.searchParams.set(attribute, text); }); } return new Request(url.href); }, response: async ({ body }) => { const { items, totalSize, hasMore } = body; return { data: items, totalSize, hasMore }; }, }, }, }) ); }, [props.activity]); return ( . . .
-
Save the
ParentContainer2.tsx
file.Your
ParentContainer2.tsx
file should look similar to ParentContainer2.tsx.txt. -
Navigate to the
JET-Virtual-DOM-app/src/components/ActivityItem
directory and open theActivityItemContainer.tsx
file in an editor. -
At the start of the
ActivityItemContainer.tsx
file, import theRESTDataProvider
module and comment or delete the import statement for theMutableArrayDataProvider
module.import { h, ComponentProps } from "preact"; . . . // import MutableArrayDataProvider = require("ojs/ojmutablearraydataprovider"); import { RESTDataProvider } from "ojs/ojrestdataprovider"; . . .
-
In the
Props
type alias, modify thedata
property to reference theRESTDataProvider
type instead of the preexisting typeMutableArrayDataProvider<Activity["id"], Activity>
.type Props = { // data?: MutableArrayDataProvider<ActivityItem["id"], ActivityItem>; data?: RESTDataProvider<ActivityItem['id'], ActivityItem>; selectedActivity: Item | null; onItemChanged: (item: Item) => void; };
-
Save the
ActivityItemContainer.tsx
file.Your
ActivityItemContainer.tsx
file should look similar to ActivityItemContainer.tsx.txt.
Task 6: Test the Virtual DOM App
-
In the terminal window, change to the
JET-Virtual-DOM-app
directory and run the virtual DOM app.npx ojet serve
-
In the browser window, view the dynamic changes in your virtual DOM app.
-
Close the browser window or tab that displays your running virtual DOM app.
-
In the terminal window, press Ctrl+C, and if prompted, enter
y
to exit the Oracle JET tooling batch job. -
In the terminal window, run the virtual DOM app using the following additional command-line arguments.
npx ojet serve --server-port=8144 --livereload-port=8145
On this occasion, the virtual DOM app displays the following message because the REST service that it trys to access only accepts requests on the server port that the
ojet serve
command uses by default (8000
), so the attempt by the RESTDataProvider to fetch from the REST service failed.Sorry that we couldn't get your product information right now. Please contact your system administrator.
Next Step
To proceed to the next tutorial in this learning path, click here.
More Learning Resources
Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.
For product documentation, visit Oracle Help Center.
Fetch data from a REST API in an Oracle JET virtual DOM app
F70627-04
July 2024