The purpose of this document is to walk through the core uses of the @edx/frontend-platform repository. This repository houses a number of core utilities for working in frontend javascript code in the openedx ecosystem.

The repo is available here and contains pretty good high level documentation in its README, as well as a link to automatically generated jsdocs.

How should I use it?

App setup/Configuration

App-State Subscription

APP_READY (init)

When configuring your app, wrap the initial app code in a subscription to the APP_READY event. This waits until the top-level initialization sequence has finished before rendering the jsx.

APP_INIT_ERROR (error)

Alongside your app code, you should provide an error presentation, by default based on the provided ErrorPage component.

subscribe(APP_INIT_ERROR, (error) => {
  ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
});

React Component Wrappers

AppProvider

Your top-level component for your component should be wrapped in an AppProvider with an optional redux store param.

subscribe(APP_READY, () => {
  ReactDOM.render(
    <AppProvider store={myReduxReducer}>
      <HelloWorld />
    </AppProvider>
  )
});

This will provide the following to HelloWorld:

Page Routes

Each “Page” within your app that you want trackable separately should be wrapped in a unique <PageRoute> or <AuthenticatedPageRoute> component (depending on the authentication requirements of the page). This will trigger the analytics sendPageEvent action for that page.

Service Initialization

You should call initialize at the top level of your app to invoke the application initialization sequence.

As part of this, you should pass in i18n messages to initialize the translation service. The general pattern of messages to add is:

  1. appMessages (custom messages defined in the individual MFE).

  2. headerMessages (messages exported from @edx/frontend-component-header)

  3. footerMessages (messages exported from @edx/frontend-component-footer)

  4. paragonMessages (messages exported from @edx/paragon).

initialize({
  handlers: {
    config: () => {
      mergeConfig({ ...custom config overrides }),
    }
  },
  messages: [
   appMessages,
   headerMessages,
   footerMessages,
   paragonMessages,
  ],
});

This will run through a number of lifecycle phases, during which related services will be configured.

options:

Authenticated network requests and user info

When working with openedx API endpoints that require authenticated user clients, you should use the provided client from @edx/frontend-platform/auth.

import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';

const getUsername= () => {
  const { username } = getAuthenticatedUser();
  return username;
};
const myPostRequest = getAuthenticatedHttpClient().post(...);
const myGetRequest = getAuthenticatedHttpClient().get(...);

As a note, if you are using @tanstack/react-query for your api calls, you can still use this interface to supply authentication for that client. An example can be found here.

Logging

The system uses the following default services for logging and analytics:

As long as you call initialize at the top level of your app and wrap your pages in PageRoute or AuthenticatedPageRoute components, you should get page and identify messages sent through the analytics service automatically.

Note: Segment configuration does rely on having a SEGMENT_KEY configured for the repo (generally empty locally and overridden in edx-internal.

From there you can send a variety of types of messages/signals:

Consume AppContext (for environment config or user object)

The generated Context object from the AppRoute component wrapper is accessible from anywhere descendant from that component, and in all the ways a normal react Context would be accessible.
The default shape of this object is { authenticatedUser, config }, where config is drawn from environment variables and app-level overrides by @edx/frontend-platform/config’s getConfig method.

default config fields defined here: https://openedx.github.io/frontend-platform/module-Config.html .

What is in it? (Deeper Dive)

Core Libraries:

Note: The JSDoc utilities are more helpful here as API docs than as guides, given the complexity/breadth of these libraries

Root exports

Authentication

@edx/frontend-platform/auth

Simplifies the process of making authenticated API requests to backend edX services by providing common authN/authZ client code that enables the login/logout flow and handles ensuring the presence of a valid JWT cookie.

Note: The documentation for AxiosJwtAuthService is nearly the same as that for the top-level auth interface, except that it contains some Axios-specific details.

Initialization

Generally, you will configure your api connection at the top level of your app in your initialize call.

Note: The JSDocs lay out a potential pattern for directly configuring an authenticated client if you are not using module-level initialize, but it is recommended to use the top-level configuration wherever possible.

Axios Utilities

Login/Logout Utilities

React

The React module provides a variety of React components, hooks, and contexts for use in an application.

Utilities

App-Level Context

AppContext provides data from App in a way that React components can readily consume, even if it's mutable data. AppContext contains the following data structure:

{
  authenticatedUser: <THE App.authenticatedUser OBJECT>,
  config: <THE App.config OBJECT>
}

If the App.authenticatedUser or App.config data changes, AppContext will be updated accordingly and pass those changes onto React components using the context.

AppContext is used in a React application like any other React Context

Components

App Provider

<AppProvider store? />

A wrapper component for React-based micro-frontends to initialize a number of common data/ context providers.

subscribe(APP_READY, () => {
  ReactDOM.render(
    <AppProvider>
      <HelloWorld />
    </AppProvider>
  )
});

This will provide the following to HelloWorld:

Login Redirect

<LoginRedirect />

A React component that, when rendered, redirects to the login page as a side effect. Uses redirectToLogin to perform the redirect.

Page Routes

<PageRoute redirectUrl />

A react-router Route component that calls sendPageEvent when it becomes active.

<AuthenticatedPageRoute redirectUrl />

A react-router route that redirects to the login page when the route becomes active and the user is not authenticated. If the application has been initialized with requireAuthenticatedUser false, an AuthenticatedPageRoute can be used to protect a subset of the application's routes, rather than the entire application.

It can optionally accept an override URL to redirect to instead of the login page.

Like a PageRoute, also calls sendPageEvent when the route becomes active.

Error Page

<ErrorPage />

An error page that displays a generic message for unexpected errors. Also contains a "Try Again" button to refresh the page.

Internationalization

@edx/frontend-platform/i18n

The i18n module relies on react-intl and re-exports all of that package's exports.

For each locale we want to support, react-intl needs 1) the locale-data, which includes information about how to format numbers, handle plurals, etc., and 2) the translations, as an object holding message id / translated string pairs. A locale string and the messages object are passed into the IntlProvider element that wraps your element hierarchy.

Note that react-intl has no way of checking if the translations you give it actually have anything to do with the locale you pass it; it will happily use whatever messages object you pass in. However, if the locale data for the locale you passed into the IntlProvider was not correctly installed with addLocaleData, all of your translations will fall back to the default (in our case English), even if you gave IntlProvider the correct messages object for that locale.

Initialization

configure({ loggingService, config, messages })

Configures the i18n library with messages for your application.

Note: Logs a warning if it detects a locale it doesn't expect (as defined by the supportedLocales list above), or if an expected locale is not provided.

General translation

Provider

<IntlProvider />

Note: This is provided by the top-level AppProvider component.

Components

Cleanest implementation, but requires importing different component format type, and not all components from react-intl have made their way into the library yet.

const myMessageObject = {
  id: 'my.message',
  defaultMessage: 'My Default Message',
  description: 'A simple example',
}
const MyComponent = () => (<>
  <FormattedMessage {...myMessageObject} />
  <FormattedDate value={new Date()} />
</>);

Available formatting components:

Imperative API (Hooks)

useIntl() hook forwards from react-intl. Relies on being contained within an IntlProvider

Usage:

const { formatMessage, formatDate, ... } = useIntl();
const myMessageObject = {
  id: 'my.message',
  defaultMessage: 'My Default Message',
  description: 'A simple example',
}
const myTranslatedMessage = formatMessage(myMessageObject);
const myTranslatedDate = formatDate(new Date());

Available formatters:

RTL Utilities

handleRtl()

Handles applying the RTL stylesheet and "dir=rtl" attribute to the html tag if the current locale is a RTL language.

isRtl(locale)

Determines if the provided locale is a right-to-left language.

Analytics

@edx/frontend-platform/analytics

Contains a shared interface for tracking events. Has a default implementation of SegmentAnalyticsService, which supports Segment and the Tracking Log API (hosted in LMS).

Initialization

Generally, you will configure your analytics interface at the top level of your app in your initialize call.

Note: The JSDocs lay out a potential pattern for directly configuring an analytics interface if you are not using module-level initialize, but it is recommended to use the top-level configuration wherever possible.

Utilities

Identify Event

Sends Identify call to Segment

NOTE: This field is generally called automatically by the top-level initialize call

identifyAnonymousUser(traits)Promise

identifyAuthenticatedUser(userId, traits)

PageEvent

Sends a page event to Segment and downstream

NOTE: This field is generally set automatically by the PageRoute and AuthenticatedPageRoute components provided in @edx/frontend-platform/react

sendPageEvent(category, name, properties)

Tracking Event - Sends a track event to Segment and downstream

sendTrackEvent(eventName, properties)

Track Logging Event - Logs events to tracking log and downstream

sendTrackingLogEvent(eventName properties)

Logging

Contains a shared interface for logging information (The default implementation is in NewRelicLoggingService.js). When in development mode, all messages will instead be sent to the console.

Initialization

Generally, you will configure your logging interface at the top level of your app in your initialize call.

Note: The JSDocs lay out a potential pattern for directly configuring an logging interface if you are not using module-level initialize, but it is recommended to use the top-level configuration wherever possible.

Utilities

Info Log

Logs a message to the 'info' log level. Can accept custom attributes as a property of the error object, or as an optional second parameter.

logInfo(infoStringOrErrorObject, customAttributes?)

Error Log

Logs a message to the 'error' log level. Can accept custom attributes as a property of the error object, or as an optional second parameter.

logError(errorStringOrObject, customAttributes?)