CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-popper

Official library to use Popper on React projects

Pending
Overview
Eval results
Files

hook-interface.mddocs/

Hook Interface

Programmatic hook interface for low-level control over Popper instances without component wrappers. The usePopper hook provides direct access to Popper.js functionality with React integration, ideal for custom implementations and advanced use cases.

Capabilities

usePopper Hook

Provides programmatic control over Popper instances with automatic lifecycle management and state synchronization.

/**
 * Low-level hook for programmatic control over Popper instances
 * @param referenceElement - The reference element to position relative to
 * @param popperElement - The popper element to position
 * @param options - Configuration options for the Popper instance
 * @returns Object containing styles, attributes, state, and control functions
 */
function usePopper<Modifiers>(
  referenceElement?: Element | PopperJS.VirtualElement | null,
  popperElement?: HTMLElement | null,
  options?: Omit<Partial<PopperJS.Options>, 'modifiers'> & {
    createPopper?: typeof PopperJS.createPopper;
    modifiers?: ReadonlyArray<Modifier<Modifiers>>;
  }
): UsePopperResult;

interface UsePopperResult {
  /** Computed styles for all positioned elements (popper, arrow, etc.) */
  styles: { [key: string]: React.CSSProperties };
  /** HTML attributes for elements (data-popper-placement, etc.) */
  attributes: { [key: string]: { [key: string]: string } | undefined };
  /** Current Popper.js state object (null if not initialized) */
  state: PopperJS.State | null;
  /** Function to manually update popper positioning */
  update: PopperJS.Instance['update'] | null;
  /** Function to force immediate positioning update */
  forceUpdate: PopperJS.Instance['forceUpdate'] | null;
}

Hook Options

The usePopper hook accepts all standard Popper.js options plus React-specific enhancements:

interface UsePopperOptions {
  /** Preferred placement for the popper element */
  placement?: PopperJS.Placement;
  /** Positioning strategy - 'absolute' or 'fixed' */
  strategy?: PopperJS.PositioningStrategy;
  /** Array of Popper.js modifiers */
  modifiers?: ReadonlyArray<Modifier<any>>;
  /** Callback fired after first positioning update */
  onFirstUpdate?: (state: Partial<PopperJS.State>) => void;
  /** Custom createPopper function (for custom Popper builds) */
  createPopper?: typeof PopperJS.createPopper;
}

Usage Examples:

import React from "react";
import { usePopper } from "react-popper";

// Basic hook usage
function BasicHookExample() {
  const [referenceElement, setReferenceElement] = React.useState(null);
  const [popperElement, setPopperElement] = React.useState(null);
  const [arrowElement, setArrowElement] = React.useState(null);

  const { styles, attributes } = usePopper(referenceElement, popperElement, {
    placement: "top",
    modifiers: [
      { name: "arrow", options: { element: arrowElement } },
      { name: "offset", options: { offset: [0, 8] } },
    ],
  });

  return (
    <>
      <button ref={setReferenceElement}>Reference Button</button>
      <div
        ref={setPopperElement}
        style={styles.popper}
        {...attributes.popper}
      >
        Popper content
        <div ref={setArrowElement} style={styles.arrow} />
      </div>
    </>
  );
}

// Advanced hook with state management
function AdvancedHookExample() {
  const [referenceElement, setReferenceElement] = React.useState(null);
  const [popperElement, setPopperElement] = React.useState(null);
  const [visible, setVisible] = React.useState(false);

  const { styles, attributes, state, update, forceUpdate } = usePopper(
    referenceElement,
    popperElement,
    {
      placement: "bottom-start",
      strategy: "fixed",
      modifiers: [
        {
          name: "preventOverflow",
          options: {
            boundary: "viewport",
            padding: 8,
          },
        },
        {
          name: "flip",
          options: {
            fallbackPlacements: ["top-start", "bottom-end", "top-end"],
          },
        },
      ],
      onFirstUpdate: (state) => {
        console.log("Initial popper state:", state);
      },
    }
  );

  // Manually update positioning when needed
  const handleUpdate = async () => {
    if (update) {
      const newState = await update();
      console.log("Updated state:", newState);
    }
  };

  // Force update immediately
  const handleForceUpdate = () => {
    if (forceUpdate) {
      const newState = forceUpdate();
      console.log("Force updated state:", newState);
    }
  };

  return (
    <div>
      <button
        ref={setReferenceElement}
        onClick={() => setVisible(!visible)}
      >
        Toggle Popper
      </button>
      
      {visible && (
        <div
          ref={setPopperElement}
          style={{
            ...styles.popper,
            background: "white",
            border: "1px solid #ccc",
            borderRadius: "4px",
            padding: "12px",
            boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
            zIndex: 1000,
          }}
          {...attributes.popper}
        >
          <div>Advanced Popper Content</div>
          {state && (
            <div style={{ fontSize: "12px", color: "#666", marginTop: "8px" }}>
              <div>Placement: {state.placement}</div>
              <div>Strategy: {state.options.strategy}</div>
            </div>
          )}
          <div style={{ marginTop: "8px" }}>
            <button onClick={handleUpdate}>Update</button>
            <button onClick={handleForceUpdate}>Force Update</button>
          </div>
        </div>
      )}
    </div>
  );
}

// Custom createPopper function
function CustomPopperExample() {
  const [referenceElement, setReferenceElement] = React.useState(null);
  const [popperElement, setPopperElement] = React.useState(null);

  // Custom Popper configuration
  const customCreatePopper = React.useCallback(
    (reference, popper, options) => {
      // Add custom logic or use a custom Popper build
      return PopperJS.createPopper(reference, popper, {
        ...options,
        // Add default modifiers or override behavior
        modifiers: [
          ...options.modifiers,
          {
            name: "customModifier",
            enabled: true,
            phase: "beforeWrite",
            fn: ({ state }) => {
              // Custom positioning logic
              console.log("Custom modifier executed:", state);
            },
          },
        ],
      });
    },
    []
  );

  const { styles, attributes } = usePopper(referenceElement, popperElement, {
    createPopper: customCreatePopper,
    placement: "top",
  });

  return (
    <>
      <button ref={setReferenceElement}>Custom Popper</button>
      <div
        ref={setPopperElement}
        style={styles.popper}
        {...attributes.popper}
      >
        Custom Popper Content
      </div>
    </>
  );
}

Virtual Elements

The usePopper hook supports virtual elements for positioning relative to coordinates rather than DOM elements:

interface VirtualElement {
  getBoundingClientRect(): DOMRect;
  contextElement?: Element;
}

Virtual Element Example:

function VirtualElementExample() {
  const [popperElement, setPopperElement] = React.useState(null);
  
  // Create virtual element at mouse position
  const virtualElement = React.useMemo(() => ({
    getBoundingClientRect: () => ({
      width: 0,
      height: 0,
      top: 100,
      right: 100,
      bottom: 100,
      left: 100,
      x: 100,
      y: 100,
      toJSON: () => ({}),
    }),
  }), []);

  const { styles, attributes } = usePopper(virtualElement, popperElement, {
    placement: "bottom",
  });

  return (
    <div
      ref={setPopperElement}
      style={styles.popper}
      {...attributes.popper}
    >
      Positioned at coordinates (100, 100)
    </div>
  );
}

Lifecycle Management

The usePopper hook automatically manages Popper instance lifecycle:

  1. Creation: Creates Popper instance when both elements are available
  2. Updates: Updates configuration when options change
  3. Cleanup: Destroys instance when elements are removed or component unmounts

Performance Considerations

  • Memoization: Options are memoized to prevent unnecessary recreations
  • Lazy initialization: Popper instance is only created when both elements exist
  • Efficient updates: Uses React's batching for optimal re-rendering

Error Handling

The hook handles various edge cases gracefully:

  • Null elements: Safe handling when reference or popper elements are null
  • Options changes: Smooth updates when configuration changes
  • Cleanup: Proper cleanup prevents memory leaks

Best Practices

  1. Memoize complex options:

    const options = React.useMemo(() => ({
      placement: "top",
      modifiers: [{ name: "offset", options: { offset: [0, 8] } }],
    }), []);
  2. Handle loading states:

    const { styles, attributes, state } = usePopper(ref1, ref2, options);
    
    if (!state) {
      return <div>Loading...</div>;
    }
  3. Use consistent element references:

    // ✅ Stable references
    const [refElement, setRefElement] = useState(null);
    const [popperElement, setPopperElement] = useState(null);
    
    // ❌ Avoid creating new references on each render
    const refElement = useRef(null).current;
  4. Combine with other hooks for complex behavior:

    function useTooltip() {
      const [referenceElement, setReferenceElement] = useState(null);
      const [popperElement, setPopperElement] = useState(null);
      const [visible, setVisible] = useState(false);
      
      const popper = usePopper(referenceElement, popperElement, {
        placement: "top",
      });
      
      return {
        ...popper,
        visible,
        setVisible,
        setReferenceElement,
        setPopperElement,
      };
    }

Install with Tessl CLI

npx tessl i tessl/npm-react-popper

docs

context-management.md

hook-interface.md

index.md

positioned-elements.md

reference-handling.md

tile.json