or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

development-tools.mdevent-handling.mdindex.mdlifecycle-management.mdreference-management.mdstate-management.mdtiming-utilities.mdutility-hooks.md
tile.json

utility-hooks.mddocs/

Utility Hooks

Additional utility hooks for unique ID generation, target element resolution, and async operations.

Capabilities

useId

Hook to generate a unique ID in the global scope (spanning across duplicate copies of the same library). Essential for accessibility and form elements that need unique identifiers.

/**
 * Hook to generate a unique ID in the global scope (spanning across duplicate copies of the same library).
 * @param prefix - Optional prefix for the ID
 * @param providedId - Optional id provided by a parent component. Defaults to the provided value if present, without conditioning the hook call
 * @returns The ID string
 */
function useId(prefix?: string, providedId?: string): string;

Usage Examples:

import { useId } from "@fluentui/react-hooks";

// Basic form field with unique ID
function FormField({ label, ...inputProps }) {
  const fieldId = useId('field');

  return (
    <div>
      <label htmlFor={fieldId}>{label}</label>
      <input id={fieldId} {...inputProps} />
    </div>
  );
}

// Accessible form with multiple fields
function AccessibleForm() {
  const nameId = useId('name');
  const emailId = useId('email');
  const descriptionId = useId('desc');

  return (
    <form>
      <div>
        <label htmlFor={nameId}>Name:</label>
        <input id={nameId} type="text" />
      </div>
      
      <div>
        <label htmlFor={emailId}>Email:</label>
        <input id={emailId} type="email" />
      </div>
      
      <div>
        <label htmlFor={descriptionId}>Description:</label>
        <textarea id={descriptionId} aria-describedby={`${descriptionId}-help`} />
        <div id={`${descriptionId}-help`}>
          Please provide a brief description.
        </div>
      </div>
    </form>
  );
}

// Using provided ID (controlled ID)
function ControlledIdComponent({ id, children }) {
  const componentId = useId('component', id);

  return (
    <div id={componentId}>
      {children}
    </div>
  );
}

// Complex aria relationships
function TabsComponent({ tabs }) {
  const tabsId = useId('tabs');
  const panelId = useId('panel');
  const [activeTab, setActiveTab] = useState(0);

  return (
    <div>
      <div role="tablist" aria-labelledby={`${tabsId}-label`}>
        {tabs.map((tab, index) => (
          <button
            key={index}
            id={`${tabsId}-${index}`}
            role="tab"
            aria-selected={index === activeTab}
            aria-controls={`${panelId}-${index}`}
            onClick={() => setActiveTab(index)}
          >
            {tab.title}
          </button>
        ))}
      </div>
      
      {tabs.map((tab, index) => (
        <div
          key={index}
          id={`${panelId}-${index}`}
          role="tabpanel"
          aria-labelledby={`${tabsId}-${index}`}
          hidden={index !== activeTab}
        >
          {tab.content}
        </div>
      ))}
    </div>
  );
}

useTarget

Hook to calculate and cache the target element specified by the given target attribute, as well as the target element's (or host element's) parent window. Commonly used for positioning elements relative to other elements.

/**
 * Hook to calculate and cache the target element specified by the given target attribute,
 * as well as the target element's (or host element's) parent window
 * @param target - Target selector passed to the component as a property
 * @param hostElement - The host element, used for determining the parent window.
 * @returns Tuple of [targetRef, targetWindow]
 */
function useTarget<TElement extends HTMLElement = HTMLElement>(
  target: Target | undefined,
  hostElement?: React.RefObject<TElement | null>
): Readonly<[React.RefObject<Element | MouseEvent | Point | Rectangle | null>, Window | undefined]>;

type Target = Element | string | MouseEvent | Point | Rectangle | null | React.RefObject<Element>;

Usage Examples:

import { useTarget } from "@fluentui/react-hooks";

// Tooltip positioning
function Tooltip({ target, children, isVisible }) {
  const hostRef = useRef<HTMLDivElement>(null);
  const [targetRef, targetWindow] = useTarget(target, hostRef);
  const [position, setPosition] = useState({ top: 0, left: 0 });

  useEffect(() => {
    if (isVisible && targetRef.current && targetWindow) {
      const targetElement = targetRef.current;
      
      if ('getBoundingClientRect' in targetElement) {
        const rect = targetElement.getBoundingClientRect();
        setPosition({
          top: rect.bottom + targetWindow.scrollY,
          left: rect.left + targetWindow.scrollX
        });
      }
    }
  }, [isVisible, targetRef, targetWindow]);

  if (!isVisible) return null;

  return (
    <div
      ref={hostRef}
      style={{
        position: 'absolute',
        top: position.top,
        left: position.left,
        background: 'black',
        color: 'white',
        padding: '4px 8px',
        borderRadius: '4px',
        zIndex: 1000
      }}
    >
      {children}
    </div>
  );
}

// Dropdown positioning
function Dropdown({ target, items, isOpen }) {
  const dropdownRef = useRef<HTMLDivElement>(null);
  const [targetRef, targetWindow] = useTarget(target, dropdownRef);

  useEffect(() => {
    if (isOpen && targetRef.current && dropdownRef.current && targetWindow) {
      const targetElement = targetRef.current;
      
      if ('getBoundingClientRect' in targetElement) {
        const targetRect = targetElement.getBoundingClientRect();
        const dropdownElement = dropdownRef.current;
        
        // Position below target
        dropdownElement.style.position = 'absolute';
        dropdownElement.style.top = `${targetRect.bottom + targetWindow.scrollY}px`;
        dropdownElement.style.left = `${targetRect.left + targetWindow.scrollX}px`;
        dropdownElement.style.minWidth = `${targetRect.width}px`;
      }
    }
  }, [isOpen, targetRef, targetWindow]);

  if (!isOpen) return null;

  return (
    <div
      ref={dropdownRef}
      style={{
        background: 'white',
        border: '1px solid #ccc',
        borderRadius: '4px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
        zIndex: 1000
      }}
    >
      {items.map((item, index) => (
        <div key={index} style={{ padding: '8px 12px' }}>
          {item}
        </div>
      ))}
    </div>
  );
}

// Context menu
function ContextMenu({ target, items, isVisible }) {
  const menuRef = useRef<HTMLDivElement>(null);
  const [targetRef] = useTarget(target, menuRef);

  useEffect(() => {
    if (isVisible && targetRef.current && menuRef.current) {
      const targetElement = targetRef.current;
      
      // Handle MouseEvent (right-click)
      if ('clientX' in targetElement && 'clientY' in targetElement) {
        const menuElement = menuRef.current;
        menuElement.style.position = 'fixed';
        menuElement.style.top = `${targetElement.clientY}px`;
        menuElement.style.left = `${targetElement.clientX}px`;
      }
    }
  }, [isVisible, targetRef]);

  if (!isVisible) return null;

  return (
    <div
      ref={menuRef}
      style={{
        background: 'white',
        border: '1px solid #ccc',
        borderRadius: '4px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
        zIndex: 1000
      }}
    >
      {items.map((item, index) => (
        <button
          key={index}
          style={{
            display: 'block',
            width: '100%',
            padding: '8px 12px',
            border: 'none',
            background: 'none',
            textAlign: 'left',
            cursor: 'pointer'
          }}
          onClick={item.onClick}
        >
          {item.label}
        </button>
      ))}
    </div>
  );
}

// Using different target types
function FlexibleTargeting() {
  const [tooltipTarget, setTooltipTarget] = useState<Target>(null);
  const buttonRef = useRef<HTMLButtonElement>(null);

  return (
    <div>
      {/* Target by ref */}
      <button 
        ref={buttonRef}
        onMouseEnter={() => setTooltipTarget(buttonRef)}
        onMouseLeave={() => setTooltipTarget(null)}
      >
        Hover for tooltip (ref target)
      </button>

      {/* Target by selector */}
      <button 
        id="selector-target"
        onMouseEnter={() => setTooltipTarget('#selector-target')}
        onMouseLeave={() => setTooltipTarget(null)}
      >
        Hover for tooltip (selector target)
      </button>

      {/* Target by element */}
      <button 
        onMouseEnter={(e) => setTooltipTarget(e.currentTarget)}
        onMouseLeave={() => setTooltipTarget(null)}
      >
        Hover for tooltip (element target)
      </button>

      <Tooltip target={tooltipTarget} isVisible={!!tooltipTarget}>
        This is a tooltip!
      </Tooltip>
    </div>
  );
}

useAsync

Hook to provide an Async instance that is automatically cleaned up on dismount. The Async utility helps manage asynchronous operations with automatic cancellation.

/**
 * Hook to provide an Async instance that is automatically cleaned up on dismount.
 * @returns Async instance from @fluentui/utilities
 */
function useAsync(): Async;

Usage Examples:

import { useAsync } from "@fluentui/react-hooks";

// Data fetching with cleanup
function DataFetchingComponent({ userId }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const async = useAsync();

  const fetchUserData = useCallback(async () => {
    setLoading(true);
    setError(null);

    try {
      // Use async.fetch for automatic cleanup
      const response = await async.fetch(`/api/users/${userId}`);
      const userData = await response.json();
      setData(userData);
    } catch (err) {
      if (!async.isDisposed) {
        setError(err.message);
      }
    } finally {
      if (!async.isDisposed) {
        setLoading(false);
      }
    }
  }, [userId, async]);

  useEffect(() => {
    fetchUserData();
  }, [fetchUserData]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>User: {data?.name}</div>;
}

// Debounced search
function SearchComponent({ onSearch }) {
  const [query, setQuery] = useState('');
  const async = useAsync();

  useEffect(() => {
    if (query.trim()) {
      // Debounce search requests
      const timeoutId = async.setTimeout(() => {
        onSearch(query);
      }, 300);

      return () => {
        async.clearTimeout(timeoutId);
      };
    }
  }, [query, onSearch, async]);

  return (
    <input
      type="text"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search..."
    />
  );
}

// Multiple async operations
function MultiAsyncComponent() {
  const [results, setResults] = useState([]);
  const async = useAsync();

  useEffect(() => {
    const fetchAllData = async () => {
      try {
        // Start multiple async operations
        const promises = [
          async.fetch('/api/data1'),
          async.fetch('/api/data2'),
          async.fetch('/api/data3')
        ];

        const responses = await Promise.all(promises);
        const data = await Promise.all(
          responses.map(response => response.json())
        );

        if (!async.isDisposed) {
          setResults(data);
        }
      } catch (error) {
        console.error('Failed to fetch data:', error);
      }
    };

    fetchAllData();
  }, [async]);

  return (
    <div>
      {results.map((result, index) => (
        <div key={index}>{JSON.stringify(result)}</div>
      ))}
    </div>
  );
}

useIsomorphicLayoutEffect (Re-export)

/**
 * useLayoutEffect that works in SSR environments
 * Re-exported from @fluentui/utilities for convenience
 */
function useIsomorphicLayoutEffect(
  effect: React.EffectCallback,
  deps?: React.DependencyList
): void;

Usage Examples:

import { useIsomorphicLayoutEffect } from "@fluentui/react-hooks";

// DOM measurements that work in SSR
function MeasurementComponent() {
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
  const elementRef = useRef<HTMLDivElement>(null);

  useIsomorphicLayoutEffect(() => {
    if (elementRef.current) {
      const { width, height } = elementRef.current.getBoundingClientRect();
      setDimensions({ width, height });
    }
  }, []);

  return (
    <div ref={elementRef}>
      Element size: {dimensions.width} x {dimensions.height}
    </div>
  );
}