All files / platform/core/src/hooks useCustomization.ts

100% Statements 23/23
100% Branches 9/9
100% Functions 7/7
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63                                    57792x 21240x 21240x   21240x 24024x 21240x   21240x 1392x                 1392x       1392x                 1392x       4176x     588x   45264x        
import { useState, useEffect } from 'react';
import { useSystem } from '../contextProviders/SystemProvider';
 
/**
 * Reads a customization and re-renders when customizations change.
 *
 * `customizationService.getCustomization` called directly during render
 * captures whatever is registered at that moment. Registration order is not
 * guaranteed: mode-scope customizations are registered in `mode.onModeEnter`,
 * which can run after panels have already rendered, and both the React
 * Compiler (which memoizes the call on the stable service reference) and
 * components that snapshot the value would then keep serving the stale
 * pre-registration value. This hook subscribes to the service's modification
 * events, so consumers always converge on the currently registered value.
 *
 * The setState updater form is used everywhere because a customization value
 * may itself be a function.
 */
export function useCustomization<T = unknown>(customizationId: string): T {
  const { servicesManager } = useSystem();
  const { customizationService } = servicesManager.services;
 
  const [value, setValue] = useState<T>(
    () => customizationService.getCustomization(customizationId) as T
  );
 
  useEffect(() => {
    const update = () => {
      // getCustomization caches the transformed value in
      // `transformedCustomizations`, so as long as nothing has invalidated that
      // cache an unchanged customization returns the same reference and
      // setState bails out. Every setter clears the cache before broadcasting,
      // so the read right after an event does re-run transform/reference
      // resolution - values that resolve by identity (functions, plain values,
      // anything without `$transform`/`$reference`) still bail out, which
      // covers the common case.
      setValue(() => customizationService.getCustomization(customizationId) as T);
    };
 
    // Catch registrations that happened between render and effect.
    update();
 
    // Mode-scope registrations are the ones that race component mounting (they
    // run in mode.onModeEnter), but all three scopes can be written at runtime
    // - global by URL-driven overrides and by any consumer calling
    // setCustomizations with an explicit scope, default by a second init pass.
    // Since getCustomization resolves across all three by priority, a hook that
    // only watched one scope would serve a stale value after a write to either
    // of the others. The bail-out above keeps the extra events cheap.
    const subscriptions = [
      customizationService.EVENTS.MODE_CUSTOMIZATION_MODIFIED,
      customizationService.EVENTS.GLOBAL_CUSTOMIZATION_MODIFIED,
      customizationService.EVENTS.DEFAULT_CUSTOMIZATION_MODIFIED,
    ].map(event => customizationService.subscribe(event, update));
 
    return () => {
      subscriptions.forEach(subscription => subscription.unsubscribe());
    };
  }, [customizationService, customizationId]);
 
  return value;
}