Broadleaf Microservices
  • v1.0.0-latest-prod

Commerce Shared React Hooks

Hooks provided by @broadleaf/commerce-shared-react.

useLogout

Hook to use to log out the user. Uses the AuthClient to make the request.

Props

  • logoutActions: () ⇒ Promise<void> Actions to perform before logging out with a redirect to the auth server.

Response

  • logout([options]) ⇒ Promise<void>: An async callback function to send the request.

Example

import type { FC } from 'react';
import { useLogout } from '@broadleaf/commerce-shared-react';

interface Props {
  logoutActions?: () => Promise<void>;
}

const LogoutButton: FC<Props> = props => {
  const { logoutActions } = props;

  const logoutWithRedirect = useLogout(logoutActions);

  const handleLogout = async () => {
    await logoutWithRedirect();
  };

  return (
    <button onClick={handleLogout} type="button">
      Logout
    </button>
  );
};

useClientDiscovery

Hook to use to get the authorized client ID dynamically. This is a wrapper around getClientIdDynamically that calls the method in a useEffect and returns the client id, loading state, and error state.

Props

  • authClientId: string: The client ID to use if the mode is static. Required.

  • authClientMode?: string: The mode to use. Either static or dynamic. Optional.

    • Default is dynamic.

Response

  • clientId?: string: The client ID.

  • isLoading: boolean: Whether the request is in progress.

  • error?: unknown: Any error produced by the request.

Example

import { useClientDiscovery } from '@broadleaf/commerce-shared-react';

const MyComponent = () => {
  const { clientId, isLoading, error } = useClientDiscovery('my-client-id', 'dynamic');

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      <h1>Client ID: {clientId}</h1>
    </div>
  );
};

useClientsContext

Hook to use to get the current client context. This simply calls the useContext hook with the ClientsContext.

useGetClient

export const useGetClient = (): (<T extends Client>(name: string) => T) => {
  return useClientsContext().getClient;
};

Hook to use to get a Client from the ClientsContext by name.

Props

  • name: string: The name of the client to get.

  • T: The type of the client to get.

Response

The client.

Example

import { useGetClient } from '@broadleaf/commerce-shared-react';
import type { TenantClient } from '@broadleaf/commerce-tenant';

const MyComponent = () => {
  const getClient = useGetClient();
  const client = getClient<TenantClient>('TENANT');

  return null;
};

useLocaleContext

Hook to use to get the current locale context. This simply calls the useContext hook with the LocaleContext.

useLocaleState

Hook to use to get the current locale state. The current locale is determined by the following order of precedence:

  1. The locale query parameter in the URL.

  2. The blLocale cookie.

  3. The defaultLocale property in the application object.

  4. The browser’s navigator language preferences.

  5. The defaultLocale prop passed to the hook.

Response

The current locale.

Example

import { useLocaleState } from '@broadleaf/commerce-shared-react';
import { useEffect } from 'react';

const MyComponent = () => {
  const locale = useLocaleState({
    cachedState: 'en-US',
    cookieName: 'blLocale',
    cookieMaxAge: 3600,
    defaultLocale: 'en-US'
  });

  useEffect(() => {
    console.log('Current locale:', locale);
  }, [locale]);

  return <div>Current locale: {locale}</div>;
};

useLocationContext

Hook to use to get the current location context. This simply calls the useContext hook with the LocationContext.

useGetLocationState

Hook to use to get the current location state. This is an abstraction to the normal location API. See LocationState.

usePreviewState

Returns the current preview state from the preview context. See PreviewState.

usePreviewDispatch

Returns the dispatch method from the preview context to make updates to the current preview state. See PreviewDispatch.

usePreviewOptions

Hook to use to build the PreviewOptions to include in requests through the Broadleaf SDK clients. The values will be populated from the PreviewState.

Props

None.

Response

Example

import { useCallback, useState } from 'react';
import axios from 'axios';
import type { Cart, UpdateCartInfoRequest } from '@broadleaf/commerce-cart';
import type { ApiError } from '@broadleaf/commerce-core';
import { usePreviewOptions } from '@broadleaf/commerce-shared-react';

import { useGetCustomerAccessToken } from '@src/auth/hooks';
import { useCartClient } from '@src/common/contexts';

interface Response {
  updateCartInfo: (cart: Cart, request: UpdateCartInfoRequest) => Promise<void>;
  cart?: Cart;
  error?: ApiError | unknown;
}

export const useUpdateCartInfo = (): Response => {
  const [error, setError] = useState<ApiError | unknown>();
  const [cart, setCart] = useState<Cart>();
  const getCustomerToken = useGetCustomerAccessToken();
  const preview = usePreviewOptions(); // <-- use the hook
  const cartClient = useCartClient();

  const updateCartInfo = useCallback(
    async (cart: Cart, request: UpdateCartInfoRequest): Promise<void> => {
      try {
        setError(undefined);
        const accessToken = await getCustomerToken();
        const newCart = await cartClient.updateCartInfo(cart.id, request, {
          accessToken,
          preview, // <-- use the preview options //
          version: cart.version,
        });
        setCart(newCart);
      } catch (err) {
        if (axios.isCancel(err)) {
          return;
        }

        if (axios.isAxiosError(err)) {
          setError(err.response.data);
        }

        setError(err);
      }
    },
    [cartClient, getCustomerToken, preview]
  );

  return { updateCartInfo, error, cart };
};

useProvidePreviewContext

Hook to use to provide the preview context.

Props

  • cachedSandboxPreview: SandboxPreview: The cached sandbox preview to use. Optional.

  • cookieName: string: The name of the cookie to use. Optional.

    • Default is blSandboxPreview.

Response

An array containing:

  1. PreviewState: The current preview state.

  2. PreviewDispatch: The dispatch function to use to update the preview state.

Example

import { PreviewContext } from 'contexts';
import { FC, useEffect } from 'react';

import { PageLoader, PreviewLayout } from 'components';
import { useProvidePreviewContext } from 'hooks';

import type { PreviewProviderProps } from 'types';
import { ComponentRenderer } from 'services';

/**
 * Sets up the PreviewContext and adds the `PREVIEW_LAYOUT` component that is primarily responsible
 * for adding the `PREVIEW_HEADER` component for rendering Preview-on-Site components.
 */
export const PreviewProvider: FC<PreviewProviderProps> = props => {
  const { children, messages } = props;
  const context = useProvidePreviewContext(props);
  const [state] = context;

  useEffect(() => {
    // when these change, need to refetch all the things
    if (state.reload) {
      window.location.reload();
    }
  }, [state.reload]);

  if (state.isActive && !state.isValidated) {
    return <ComponentRenderer name="PAGE_LOADER" fallback={PageLoader} />;
  }

  if (!state.isActive && !state.isValidated) {
    return <>{children}</>;
  }

  return (
    <PreviewContext.Provider value={context}>
      <ComponentRenderer
        name="PREVIEW_LAYOUT"
        messages={messages}
        fallback={PreviewLayout}
      >
        {children}
      </ComponentRenderer>
    </PreviewContext.Provider>
  );
};

useSandboxColors

Returns the background and foreground color for the sandbox. The background color is determined by the sandbox.color property. The foreground color is determined by determining whether the background color is dark or light using a brightness calculation. If the background color is dark, the foreground color is set to white, otherwise it is set to black. See https://www.w3.org/TR/AERT/#color-contrast.

Props

Response

Example

import { useSandboxColors } from '@broadleaf/commerce-shared-react';

const MyComponent = () => {
  const sandbox = {
    color: '#ff0000'
  };

  const { background, text } = useSandboxColors(sandbox);

  return (
    <div style={{ backgroundColor: background.primary, color: text.primary }}>
      Sandbox Colors
    </div>
  );
};

useTenantContext

Hook to use to get the current tenant context. This simply calls the useContext hook with the TenantContext.

useTenantClient

Hook to use to get the current TenantClient. This will check the ClientRegistrar to find a client registered as TENANT.

useCurrentApplication

Checks the TenantContext to determine the current application.

useCurrentTenant

Checks the TenantContext to determine the current tenant.

useTenantState

Hook to get the current tenant state. It takes into account any cached state that may be present. When the cached state is updated, it will update the tenant state.

Props

  • cachedState: TenantState: The cached state to use. Optional.

Response

The current tenant state. See TenantState.

Example

useGetApplicationUrl

Hook to use to get the application URL. This is used to build the base URL when setting the silentRedirectURI for the AuthClient. It takes into account the type of identifier used for the application, so that it is included in the URL as appropriate.

Props

  • applicationParamName: string: The name of the application parameter to use in the URL.

Response

The URL of the application.

Example

import { FC } from 'react';
import { AuthProvider } from '@broadleaf/auth-react';
import { useGetApplicationUrl } from '@broadleaf/commerce-shared-react';
import type { AppAuthProviderProps } from '@broadleaf/commerce-shared-react';

const AppAuthProvider: FC<AppAuthProviderProps> = props => {
  const { children, ...rest } = props;
  const baseUrl = useGetApplicationUrl();
  const silentRedirectUri = new URL(baseUrl);
  // adding this path separately so as not to lose the parameters
  if (silentRedirectUri.pathname?.endsWith('/')) {
    silentRedirectUri.pathname += 'silent-callback.html';
  } else {
    silentRedirectUri.pathname += '/silent-callback.html';
  }

  return (
    <AuthProvider
      {...rest}
      key={props.clientId}
      redirectUri={baseUrl.href}
      silentRedirectUri={silentRedirectUri.href}
      credentials
      onRedirectCallback={appState => {
        window.location.replace(appState?.returnTo || window.location.pathname);
      }}
    >
      <AuthSessionManager>{children}</AuthSessionManager>
    </AuthProvider>
  );
};

useDelayHover

Hook to use to delay a hover action. For example, keeping a dropdown open for a second after the user’s cursor is no longer hovered over it.

Props

An object containing the following:

  • T: Element: The type of the element to use.

  • value: boolean: Whether the hover is active.

  • setValue: (value: boolean, event?: MouseEvent<T>) ⇒ void: Function to set the hover active state.

  • openDelay: number: The number of milliseconds to wait before setting the active state to true.

    • Default is 500.

  • closeDelay: number: The number of milliseconds to wait before setting the active state to false.

    • Default is 500.

  • includeEvent: boolean: Whether to pass the JavaScript event object to setValue in addition to true or false.

    • Default is false.

Response

An object containing the following:

  • handleMouseEnter: MouseEventHandler<T>: Mouse-enter callback handler.

    • Takes the JavaScript event object.

  • handleMouseLeave: MouseEventHandler<T>: Mouse-leave callback handler.

    • Takes the JavaScript event object.

Example

import { useState } from 'react';
import type { HTMLDivElement } from 'react';
import { useDelayHover } from '@broadleaf/commerce-shared-react';

const MyComponent = () => {
  const [isHovered, setIsHovered] = useState(false);

  const { handleMouseEnter, handleMouseLeave } = useDelayHover<HTMLDivElement>({
    value: isHovered,
    setValue: setIsHovered,
    openDelay: 500,
    closeDelay: 500,
    includeEvent: true
  });

  return (
    <div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
      Hover over me!
      {isHovered && <div>I'm hovered!</div>}
    </div>
  );
};

useEventCallback

Hook used to create an event callback that will not change with every render. This is necessary in situations where you want to pass an event callback as a prop without resulting in re-renders of children components. This is useful for passing event handlers to components that rely on React.memo or PureComponent. When the callback is called, it will always reference the latest value of the dependencies.

Props

  • RType: The type of the return value of the callback function.

  • fn: (…​args: never[]) ⇒ RType: The callback that you want to memoize.

  • dependencies: never[]: Dependencies that determine when the memoized callback should be updated.

Response

(…​args: never[]) ⇒ RType | undefined: The memoized callback.

Example

import { useEventCallback } from '@broadleaf/commerce-shared-react';

const MyComponent = props => {
  // the function `onChange` never mutates, but the function it internally reference does change
  const onChange = useEventCallback(e =>
    props.setFieldValue(e.target.name, e.target.value),
    // the internal function is recalculates anytime props.setFieldValue changes
    [props.setFieldValue]
  );
  return <Input onChange={onChange}/>
};

useFormatDate

Wrapper around useIntl that just returns the formatDate function. See react-intl’s documentation for more information.

useFormatMessage

Wrapper around useIntl that just returns the formatNumber function. See react-intl’s documentation for more information.

useFormatNumber

Wrapper around useIntl that just returns the formatNumber function. See react-intl’s documentation for more information.

useFormatTime

Wrapper around useIntl that just returns the formatTime function. See react-intl’s documentation for more information.

useGetCustomerAccessToken

Hook to use to make a request to fetch an access token for the customer or CSR.

Props

  • csrScope: string: The scope to use for the CSR. Optional. Defaults to CSR.

Response

A function to use to fetch the access token:

  • (options?: GetAccessTokenOptions) ⇒ Promise<string | undefined>: The function to call to fetch the access token.

    • Parameters:

    • Returns:

      • Promise<string | undefined>: The access token if successful, otherwise undefined.

Example

import { useGetCustomerAccessToken } from '@broadleaf/commerce-shared-react';

const MyComponent = () => {
  const getAccessToken = useGetCustomerAccessToken();

  const handleClick = async () => {
    const token = await getAccessToken();
    console.log('Access Token:', token);
  };

  return (
    <button onClick={handleClick}>
      Get Access Token
    </button>
  );
};

useInterval

Hook that invokes a callback function at a specified interval. When the component unmounts, the interval is cleared.

Props

  • RType: The type of the return value of the callback function.

  • callback: (…​args: never[]) ⇒ RType: The callback function to invoke.

  • delay: number | null: The interval delay in milliseconds. If null, the interval is cleared.

Response

None.

Example

import { useInterval } from '@broadleaf/commerce-shared-react';

// update the count every second
const MyComponent = () => {
  const [count, setCount] = useState(0);

  useInterval(() => {
    setCount(prevCount => prevCount + 1);
  }, 1000);

  return (
    <div>
      <h1>Count: {count}</h1>
    </div>
  );
};

useIsClient

Determines if the code is running in a client environment vs server-side.

Props

None.

Response

  • boolean: Whether the code is running in a client environment or not.

Example

import { useIsClient } from '@broadleaf/commerce-shared-react';

const MyComponent = () => {
  const isClient = useIsClient();

  return (
    <div>
      <h1>Client Check</h1>
      <p>{isClient ? 'Running in client environment' : 'Not running in client environment'}</p>
    </div>
  );
};

useIsWindowFocused

Determines if the window is focused or not.

Props

None

Response

  • boolean: Whether the window is focused or not.

Example

import { useIsWindowFocused } from '@broadleaf/commerce-shared-react';

const MyComponent = () => {
  const isFocused = useIsWindowFocused();

  return (
    <div>
      <h1>Window Focus</h1>
      <p>{isFocused ? 'Window is focused' : 'Window is not focused'}</p>
    </div>
  );
};

useUserOperations

Hook responsible for requesting the authenticated user’s permitted operations for the provided security scopes.

Props

  • scope string | string[]: The scope or scopes to check for.

Response

Info on the request including its result and a method to resend the request.

  • userOperationInfo: UserOperationInformation: The user operation info if no error is encountered.

  • errorUserOperations: ApiError | unknown: Any error produced by the request. Typically this is an ApiError.

  • loading: boolean: Whether the request is in progress.

  • refetch: () ⇒ Promise<UserOperationInformation | undefined>: Function to resend the request.

Example

import { PermissionType } from '@broadleaf/auth-web';
import type { UserOperationInformation } from '@broadleaf/commerce-shared-react';
import { useUserOperations } from '@broadleaf/commerce-shared-react';

const MyComponent = () => {
  const { userOperationInfo, errorUserOperations, loading, refetch } = useUserOperations('SUBSCRIPTION');

  if (loading) {
    return <div>Loading...</div>;
  }

  if (errorUserOperations) {
    return <div>Error: {errorUserOperations.message}</div>;
  }

  if (!hasPermission(userOperationInfo, 'SUBSCRIPTION')) {
    return <div>You do not have permission to view this information.</div>;
  }

  return (
    <div>
      <h1>User Operations</h1>
      <pre>{JSON.stringify(userOperationInfo, null, 2)}</pre>
      <button onClick={refetch}>Refetch</button>
    </div>
  );
};

const hasPermission = (
  userOperationInfo: UserOperationInformation,
  permission: string,
  types = [PermissionType.READ, PermissionType.ALL]
): boolean => {
  return (
    !!userOperationInfo &&
    !!userOperationInfo.content
      .filter(userOperation => userOperation.scope === permission)
      .filter(
        userOperation =>
          intersection(userOperation.operationTypes, types).length
      ).length
  );
};