CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-aria--utils

Essential utility functions and React hooks for building accessible React Aria UI components

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

props-and-events.mddocs/

Props & Event Utilities

Intelligent prop merging, event chaining, and DOM attribute filtering for React components. These utilities handle the complex logic of combining props from multiple sources while preserving type safety.

Capabilities

mergeProps Function

Intelligently merges multiple props objects with special handling for event handlers, CSS classes, and IDs.

/**
 * Intelligently merges multiple props objects
 * - Chains event handlers (functions starting with 'on[A-Z]')
 * - Combines CSS classes using clsx
 * - Merges IDs using mergeIds
 * - Later props override earlier ones for other properties
 * @param args - Multiple props objects to merge
 * @returns Merged props object with proper TypeScript typing
 */
function mergeProps<T extends Props[]>(...args: T): UnionToIntersection<TupleTypes<T>>;

Usage Examples:

import { mergeProps } from "@react-aria/utils";

function Button({ className, onClick, ...props }) {
  const defaultProps = {
    className: "btn btn-default",
    onClick: (e) => console.log("Button clicked"),
    type: "button" as const
  };
  
  const userProps = {
    className,
    onClick,
    ...props
  };
  
  // Event handlers are chained, classes combined, other props override
  const finalProps = mergeProps(defaultProps, userProps);
  
  return <button {...finalProps} />;
}

// Usage
<Button 
  className="btn-primary" // Results in: "btn btn-default btn-primary"
  onClick={(e) => console.log("User click")} // Both handlers will run
  disabled={true} // Overrides type: "button"
/>

chain Function

Chains multiple callback functions to execute in sequence with the same arguments.

/**
 * Chains multiple callback functions to execute in sequence
 * @param callbacks - Variable number of callback functions
 * @returns Function that calls all callbacks with same arguments
 */
function chain(...callbacks: any[]): (...args: any[]) => void;

Usage Examples:

import { chain } from "@react-aria/utils";

function MyComponent() {
  const handleClick1 = (e) => console.log("First handler");
  const handleClick2 = (e) => console.log("Second handler");
  const handleClick3 = (e) => console.log("Third handler");
  
  // Chain multiple handlers
  const chainedHandler = chain(handleClick1, handleClick2, handleClick3);
  
  return <button onClick={chainedHandler}>Click me</button>;
}

// All three handlers will execute in order when button is clicked

filterDOMProps Function

Filters props to include only valid DOM attributes, with configurable options for different use cases.

/**
 * Filters props to include only valid DOM attributes
 * @param props - Component props to filter
 * @param opts - Options object controlling which props to include
 * @returns Filtered props object safe for DOM elements
 */
function filterDOMProps(
  props: DOMProps, 
  opts?: FilterDOMPropsOptions
): DOMAttributes & AriaLabelingProps & GlobalDOMAttributes;

interface FilterDOMPropsOptions {
  /** Include ARIA labeling props (aria-label, aria-labelledby, etc.) */
  labelable?: boolean;
  /** Include link-specific DOM props (href, target, etc.) */
  isLink?: boolean;
  /** Include global DOM attributes (id, className, style, etc.) */
  global?: boolean;
  /** Include DOM event handlers (onClick, onFocus, etc.) */
  events?: boolean;
  /** Additional prop names to include */
  propNames?: Set<string>;
}

Usage Examples:

import { filterDOMProps } from "@react-aria/utils";

function MyInput({ label, onValueChange, ...props }) {
  // Filter out non-DOM props but keep labeling props
  const domProps = filterDOMProps(props, { 
    labelable: true,
    events: true 
  });
  
  return (
    <div>
      {label && <label>{label}</label>}
      <input {...domProps} />
    </div>
  );
}

// Usage - onValueChange will be filtered out, but aria-label will be kept
<MyInput 
  aria-label="Name field"
  onValueChange={(value) => console.log(value)} // Filtered out
  className="input-field" // Kept if global: true
  onClick={(e) => console.log("Clicked")} // Kept because events: true
  customProp="value" // Filtered out
/>

Advanced mergeProps Usage

Complex scenarios with multiple prop sources and type preservation:

import { mergeProps, filterDOMProps } from "@react-aria/utils";

function AdvancedButton({ variant = "primary", size = "medium", ...props }) {
  // Base props with defaults
  const baseProps = {
    className: `btn btn-${variant} btn-${size}`,
    type: "button" as const
  };
  
  // Accessibility props
  const a11yProps = {
    role: "button",
    tabIndex: 0
  };
  
  // User props (filtered for DOM safety)
  const userProps = filterDOMProps(props, { 
    labelable: true, 
    events: true,
    global: true 
  });
  
  // Merge all props in order of precedence
  const finalProps = mergeProps(baseProps, a11yProps, userProps);
  
  return <button {...finalProps} />;
}

Event Handler Chaining

Understanding how mergeProps chains event handlers:

import { mergeProps } from "@react-aria/utils";

function EventExample() {
  const props1 = {
    onClick: (e) => {
      console.log("First handler");
      e.preventDefault(); // This will still execute
    }
  };
  
  const props2 = {
    onClick: (e) => {
      console.log("Second handler");
      // This runs after props1.onClick
    }
  };
  
  const props3 = {
    onClick: (e) => {
      console.log("Third handler"); 
      // This runs after props2.onClick
    }
  };
  
  const mergedProps = mergeProps(props1, props2, props3);
  
  return <button {...mergedProps}>Click me</button>;
  // All three handlers execute in order: First, Second, Third
}

Types

interface DOMProps {
  id?: string;
}

interface DOMAttributes extends React.DOMAttributes<HTMLElement> {
  // Standard DOM event handlers and attributes
}

interface AriaLabelingProps {
  "aria-label"?: string;
  "aria-labelledby"?: string;
  "aria-describedby"?: string;
  "aria-details"?: string;
}

interface GlobalDOMAttributes {
  className?: string;
  style?: React.CSSProperties;
  hidden?: boolean;
  lang?: string;
  dir?: "ltr" | "rtl";
  // ... other global HTML attributes
}

type Props = Record<string, any>;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type TupleTypes<T> = { [P in keyof T]: T[P] };

Install with Tessl CLI

npx tessl i tessl/npm-react-aria--utils

docs

animation-and-transitions.md

event-management.md

focus-and-accessibility.md

id-and-refs.md

index.md

links-and-navigation.md

miscellaneous-utilities.md

platform-detection.md

props-and-events.md

scrolling-and-layout.md

shadow-dom-support.md

state-and-effects.md

virtual-events-and-input.md

tile.json