Write a fetcher

Typically, a widget renders your page on the server as the initial display for the widget is generated. The data needed to do this is obtained using data fetchers.

There are many default data fetchers, which you can reference from the /oracle-cx-commerce/fetchers directory, but you can also create your own.

Data fetchers are plug-ins that can be included in your application to identify what your widget needs before it can be rendered. When you use a template to create a widget, the template pulls in all default fetchers. Fetchers that you create to use with your widget are stored in your application’s /plugins/fetchers directory.

There are two types of fetchers. The first is a global fetcher that is independent of the widget. This means that the widget does not use the widgetConfig parameter. Global fetchers can be used by any widget, or by multiple widgets simultaneously. This is a generic fetcher that loads the current site:
const fetchSite = store => store.endpoint('getSite');

The other type of fetcher is a component-specific fetcher, or one that is dependent on the widget configuration. These fetchers create requests that are specific to the widget instance and may return unique results per widget instance.

The following is a generic global fetcher that loads the category menu data:
const fetchMenuCategories = (store, widgetConfig) => {
  // Get the necessary values from widget config
  const {numberOfChildCategoriesToDisplay = 3, hideCategoryIfOutOfStock = false} = widgetConfig;
 
  // Pass the widget config values to the endpoint call
  return store.endpoint('getCollectionMenu', {
    categoryId: 'rootCategory',
    filterKey: 'categoryNavData',
    expandChildren: true,
    maxLevel: numberOfChildCategoriesToDisplay,
    disableActiveProdCheck: hideCategoryIfOutOfStock
  });
};
Fetcher loads data into the state using the store.endpoint method. For example:
interface Fetcher {
  (store: Store, [widgetConfig: Object]): Promise<*>
}
Use the following steps to create an fetcher:
  1. Create the appropriate applications /plugin directory.
  2. Once you have the application directory, create a directory named /plugins/fetchers. This directory should contain an index.js that contains the following:
    /**
     * References to the application's custom data fetchers.
     */
    export {listCurrenciesFetcher} from './list-currencies';
  3. In the /plugins/fetchers directory, create a hooks.js file that contains the following:
    /**
     * References to the application's custom data fetcher hooks.
     */
    export {useListCurrenciesFetcher} from './list-currencies/hook';
  4. Now create a sub-directory within the /plugins/fetchers directory, for example /plugins/fetchers/list-currencies/. This directory will also contain an index.js and a hook.js file.
  5. Create the fetcher index.js file. For example, create the /plugins/fetchers/list-currencies/index.js file:
    /**
     * Fetcher that requests the currency list using an endpoint.
     */
    export const listCurrenciesFetcher = store => 
      store.endpoint('_listCurrencies');
    };
  6. Create the fetcher hook.js file. The hook file allows fetchers to be run inside React components. Fetcher hooks are based on the useEffect hook and run only in the browser. The useEffect hook allows you to invoke the fetcher only after the page has been rendered. For example, create the /plugins/fetchers/list-currencies/hook.js file:
    import {useEffect} from 'react';
    import {isEmptyObject} from '@oracle-cx-commerce/utils/generic';
    import {getCurrencies} from '../../selectors';
    import {listCurrenciesFetcher} from '..';
     
    /**
     * This hook will invoke the listCurrenciesFetcher if the currency
     * list is not already available in the application state.
     */
    export const useListCurrenciesFetcher = store =>
      useEffect(() => {
        if (isEmptyObject(getCurrencies(store.getState()))) {
          listCurrenciesFetcher(store);
        }
      }, [store]);
    };

It is a good practice to conditionally invoke the fetcher in the hook.js file to ensure that the fetcher is not already executed on the server-side. The above example conditionally invokes the fetcher after the isEmptyObject validation.

For performance information, refer to Prevent useEffect() from executing unnecessarily.