dgate 2.1.0

DGate API Gateway - High-performance API gateway with JavaScript module support
Documentation
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React, {
  useState,
  useContext,
  useEffect,
  type ReactNode,
  type ComponentType,
} from 'react';
import {ReactContextError, useShallowMemoObject} from '../../utils/reactUtils';

// This context represents a "global layout store". A component (usually a
// layout component) can request filling this store through
// `NavbarSecondaryMenuFiller`. It doesn't actually control rendering by itself,
// and this context should be considered internal implementation. The user-
// facing value comes from `display.tsx`, which takes the `component` and
// `props` stored here and renders the actual element.

export type NavbarSecondaryMenuComponent<Props> = ComponentType<Props>;

/** @internal */
export type Content =
  | {
      component: NavbarSecondaryMenuComponent<object>;
      props: object;
    }
  | {component: null; props: null};

type ContextValue = [
  content: Content,
  setContent: React.Dispatch<React.SetStateAction<Content>>,
];

const Context = React.createContext<ContextValue | null>(null);

/** @internal */
export function NavbarSecondaryMenuContentProvider({
  children,
}: {
  children: ReactNode;
}): JSX.Element {
  const value = useState({component: null, props: null});
  return (
    // @ts-expect-error: this context is hard to type
    <Context.Provider value={value}>{children}</Context.Provider>
  );
}

/** @internal */
export function useNavbarSecondaryMenuContent(): Content {
  const value = useContext(Context);
  if (!value) {
    throw new ReactContextError('NavbarSecondaryMenuContentProvider');
  }
  return value[0];
}

/**
 * This component renders nothing by itself, but it fills the placeholder in the
 * generic secondary menu layout. This reduces coupling between the main layout
 * and the specific page.
 *
 * This kind of feature is often called portal/teleport/gateway/outlet...
 * Various unmaintained React libs exist. Most up-to-date one:
 * https://github.com/gregberge/react-teleporter
 * Not sure any of those is safe regarding concurrent mode.
 */
export function NavbarSecondaryMenuFiller<P extends object>({
  component,
  props,
}: {
  component: NavbarSecondaryMenuComponent<P>;
  props: P;
}): JSX.Element | null {
  const context = useContext(Context);
  if (!context) {
    throw new ReactContextError('NavbarSecondaryMenuContentProvider');
  }
  const [, setContent] = context;

  // To avoid useless context re-renders, props are memoized shallowly
  const memoizedProps = useShallowMemoObject(props);

  useEffect(() => {
    // @ts-expect-error: this context is hard to type
    setContent({component, props: memoizedProps});
  }, [setContent, component, memoizedProps]);

  useEffect(
    () => () => setContent({component: null, props: null}),
    [setContent],
  );

  return null;
}