Source: react/AppProvider.jsx

import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { BrowserRouter as Router } from 'react-router-dom';

import OptionalReduxProvider from './OptionalReduxProvider';

import ErrorBoundary from './ErrorBoundary';
import AppContext from './AppContext';
import {
  useAppEvent,
  useParagonTheme,
  useTrackColorSchemeChoice,
} from './hooks';
import { paragonThemeActions } from './reducers';
import { getAuthenticatedUser, AUTHENTICATED_USER_CHANGED } from '../auth';
import { getConfig } from '../config';
import { CONFIG_CHANGED } from '../constants';
import {
  getLocale,
  getMessages,
  IntlProvider,
  LOCALE_CHANGED,
} from '../i18n';
import { basename } from '../initialize';
import { SELECTED_THEME_VARIANT_KEY } from './constants';

/**
 * 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:
 * - An error boundary as described above.
 * - An `AppContext` provider for React context data.
 * - IntlProvider for @edx/frontend-i18n internationalization
 * - Optionally a redux `Provider`. Will only be included if a `store` property is passed to
 * `AppProvider`.
 * - A `Router` for react-router.
 * - A theme manager for Paragon.
 *
 * @param {Object} props
 * @param {Object} [props.store] A redux store.
 * @memberof module:React
 */
export default function AppProvider({ store = null, children, wrapWithRouter = true }) {
  const [config, setConfig] = useState(getConfig());
  const [authenticatedUser, setAuthenticatedUser] = useState(getAuthenticatedUser());
  const [locale, setLocale] = useState(getLocale());

  useAppEvent(AUTHENTICATED_USER_CHANGED, () => {
    setAuthenticatedUser(getAuthenticatedUser());
  });

  useAppEvent(CONFIG_CHANGED, () => {
    setConfig(getConfig());
  });

  useAppEvent(LOCALE_CHANGED, () => {
    setLocale(getLocale());
  });

  useTrackColorSchemeChoice();
  const [paragonThemeState, paragonThemeDispatch] = useParagonTheme();

  const appContextValue = useMemo(() => ({
    authenticatedUser,
    config,
    locale,
    paragonTheme: {
      state: paragonThemeState,
      setThemeVariant: (themeVariant) => {
        paragonThemeDispatch(paragonThemeActions.setParagonThemeVariant(themeVariant));

        // Persist selected theme variant to localStorage.
        window.localStorage.setItem(SELECTED_THEME_VARIANT_KEY, themeVariant);
      },
    },
  }), [authenticatedUser, config, locale, paragonThemeState, paragonThemeDispatch]);

  if (!paragonThemeState?.isThemeLoaded) {
    return null;
  }

  return (
    <IntlProvider locale={locale} messages={getMessages()}>
      <ErrorBoundary>
        <AppContext.Provider
          value={appContextValue}
        >
          <OptionalReduxProvider store={store}>
            {wrapWithRouter ? (
              <Router basename={basename}>
                <div data-testid="browser-router">
                  {children}
                </div>
              </Router>
            ) : children}
          </OptionalReduxProvider>
        </AppContext.Provider>
      </ErrorBoundary>
    </IntlProvider>
  );
}

AppProvider.propTypes = {
  store: PropTypes.shape({}),
  children: PropTypes.node.isRequired,
  wrapWithRouter: PropTypes.bool,
};