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

reference-management.mddocs/

Reference Management

Utilities for working with React refs, including merging multiple refs and creating effect-based refs.

Capabilities

useMergedRefs

React hook to merge multiple React refs (either MutableRefObjects or ref callbacks) into a single ref callback that updates all provided refs. Essential for forwarding refs while also maintaining internal refs.

/**
 * React hook to merge multiple React refs into a single ref callback that updates all provided refs
 * @param refs - Refs to collectively update with one ref value.
 * @returns A function with an attached "current" prop, so that it can be treated like a RefObject.
 */
function useMergedRefs<T>(...refs: (React.Ref<T> | undefined)[]): RefObjectFunction<T>;

/**
 * A Ref function which can be treated like a ref object in that it has an attached
 * current property, which will be updated as the ref is evaluated.
 */
type RefObjectFunction<T> = React.RefObject<T> & ((value: T) => void);

Usage Examples:

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

// Basic ref forwarding with internal ref
const CustomInput = forwardRef<HTMLInputElement, { label: string }>((props, ref) => {
  const internalRef = useRef<HTMLInputElement>(null);
  const mergedRef = useMergedRefs(ref, internalRef);

  const focusInput = () => {
    internalRef.current?.focus();
  };

  return (
    <div>
      <label>{props.label}</label>
      <input ref={mergedRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
});

// Multiple ref callbacks
function MultiRefComponent() {
  const ref1 = useRef<HTMLDivElement>(null);
  const ref2 = useRef<HTMLDivElement>(null);
  
  const callbackRef1 = useCallback((element: HTMLDivElement | null) => {
    if (element) {
      console.log('Callback ref 1:', element);
    }
  }, []);

  const callbackRef2 = useCallback((element: HTMLDivElement | null) => {
    if (element) {
      console.log('Callback ref 2:', element);
    }
  }, []);

  const mergedRef = useMergedRefs(ref1, ref2, callbackRef1, callbackRef2);

  return (
    <div ref={mergedRef}>
      This element is referenced by multiple refs
    </div>
  );
}

// HOC pattern with ref forwarding
function withLogging<T extends HTMLElement>(Component: React.ComponentType<any>) {
  return forwardRef<T, any>((props, ref) => {
    const loggingRef = useCallback((element: T | null) => {
      if (element) {
        console.log('Element attached:', element.tagName);
      } else {
        console.log('Element detached');
      }
    }, []);

    const mergedRef = useMergedRefs(ref, loggingRef);

    return <Component {...props} ref={mergedRef} />;
  });
}

useRefEffect

Creates a ref, and calls a callback whenever the ref changes to a non-null value. The callback can optionally return a cleanup function that'll be called before the value changes, and when the ref is unmounted. This works around the limitation that useEffect cannot depend on ref.current.

/**
 * Creates a ref, and calls a callback whenever the ref changes to a non-null value. 
 * The callback can optionally return a cleanup function.
 * @param callback - Called whenever the ref's value changes to non-null. Can optionally return a cleanup function.
 * @param initial - (Optional) The initial value for the ref.
 * @returns A function that should be called to set the ref's value. Also has a .current member for access.
 */
function useRefEffect<T>(
  callback: (value: T) => (() => void) | void, 
  initial?: T | null
): RefCallback<T>;

/**
 * A callback ref function that also has a .current member for the ref's current value.
 */
type RefCallback<T> = ((value: T | null) => void) & React.RefObject<T>;

Usage Examples:

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

// Basic DOM manipulation on element attach/detach
function AutoFocusComponent() {
  const inputRef = useRefEffect<HTMLInputElement>(
    (element) => {
      // Called when element is attached
      element.focus();
      
      // Return cleanup function (optional)
      return () => {
        console.log('Input element is being detached');
      };
    }
  );

  return <input ref={inputRef} placeholder="Auto-focused" />;
}

// Intersection Observer setup
function VisibilityTracker({ onVisibilityChange }) {
  const elementRef = useRefEffect<HTMLDivElement>(
    (element) => {
      const observer = new IntersectionObserver(
        (entries) => {
          const isVisible = entries[0].isIntersecting;
          onVisibilityChange(isVisible);
        },
        { threshold: 0.1 }
      );

      observer.observe(element);

      // Cleanup: disconnect observer
      return () => {
        observer.disconnect();
      };
    }
  );

  return (
    <div ref={elementRef} style={{ height: '200px', background: 'lightblue' }}>
      Visibility tracked element
    </div>
  );
}

// Resize Observer
function ResizeTracker() {
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  const elementRef = useRefEffect<HTMLDivElement>(
    (element) => {
      const resizeObserver = new ResizeObserver((entries) => {
        const { width, height } = entries[0].contentRect;
        setDimensions({ width, height });
      });

      resizeObserver.observe(element);

      return () => {
        resizeObserver.disconnect();
      };
    }
  );

  return (
    <div>
      <div 
        ref={elementRef} 
        style={{ 
          resize: 'both', 
          overflow: 'auto', 
          border: '1px solid #ccc',
          minWidth: '100px',
          minHeight: '100px'
        }}
      >
        Resizable element
      </div>
      <div>Dimensions: {dimensions.width} x {dimensions.height}</div>
    </div>
  );
}

// Canvas setup with cleanup
function CanvasComponent() {
  const canvasRef = useRefEffect<HTMLCanvasElement>(
    (canvas) => {
      const context = canvas.getContext('2d');
      if (!context) return;

      // Setup canvas
      canvas.width = 400;
      canvas.height = 300;
      
      // Draw something
      context.fillStyle = 'blue';
      context.fillRect(10, 10, 100, 100);

      // Animation loop
      let animationId: number;
      const animate = () => {
        // Animation logic here
        animationId = requestAnimationFrame(animate);
      };
      animate();

      // Cleanup animation
      return () => {
        cancelAnimationFrame(animationId);
      };
    }
  );

  return <canvas ref={canvasRef} />;
}

// Third-party library integration
function ChartComponent({ data }) {
  const chartRef = useRefEffect<HTMLDivElement>(
    (element) => {
      // Initialize chart library
      const chart = new SomeChartLibrary(element, {
        data,
        responsive: true
      });

      // Cleanup chart instance
      return () => {
        chart.destroy();
      };
    }
  );

  return <div ref={chartRef} style={{ width: '100%', height: '400px' }} />;
}