npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mage-select-data-react-hook-form

v1.0.13

Published

React Hook Form integration for mage-select-data-engine. Effortless form synchronization and hydration.

Downloads

1,706

Readme

mage-select-data-react-hook-form 🎣

npm version license demo

The professional bridge between Mage Select and React Hook Form. Optimized for large-scale enterprise forms.

😩 The Pain: "Ghost Values" in Edit Mode

We've all been there: you open an Edit Form, the RHF value is userId: "999", but your select only loaded the first 10 users. Result? The select shows a raw ID or stays empty because "User 999" isn't in the list yet.

Mage Select RHF solves this with Automatic Hydration:

  1. RHF sets the initial value ("999").
  2. Mage detects it's missing from the current list.
  3. Mage calls your fetchByIds(["999"]) automatically.
  4. Your UI renders the correct label ("John Doe") instantly. No useEffect required.

🚀 Key Benefits: "Zero Effect" CRUDs

  • Declarative Initialization: Use autoInitialLoad: true to skip manual mounting effects.
  • Bi-Directional Support: Full memory management for massive multi-selects.
  • Type-Safe Form Values: Seamlessly integrates with RHF's control and name types.

💻 Usage (Standard Example)

import { useMageSelectController } from 'mage-select-data-react-hook-form';

function UserSelect({ control }) {
  const { 
    state, 
    loadMore, 
    toggleSelection, 
    setSearch,
    field 
  } = useMageSelectController({
    name: 'userId',
    control,
    autoInitialLoad: true,
    valueType: 'id',
    fetchPage: (p, s) => api.users.list(p, s),
    fetchByIds: (ids) => api.users.getBatch(ids),
    getId: (u) => u.id
  });

  return (
    <div className="mage-container">
      {/* Search Input */}
      <input 
        value={state.search} 
        onChange={(e) => setSearch(e.target.value)} 
        placeholder="Search users..."
      />

      {/* Selected Items Display */}
      <div className="selected-tags">
        {state.selectedItems.map(user => (
          <span key={user.id} className="chip">
            {user.name} 
            <button onClick={() => toggleSelection(user)}>&times;</button>
          </span>
        ))}
      </div>
      
      {/* Infinite List */}
      <ul className="select-list" onScroll={(e) => {
        const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
        if (scrollTop + clientHeight >= scrollHeight - 50 && state.hasMore) {
          loadMore();
        }
      }}>
        {state.items.map(user => (
          <li 
            key={user.id} 
            onClick={() => toggleSelection(user)}
            className={state.selectedItems.some(i => i.id === user.id) ? 'active' : ''}
          >
            {user.name}
          </li>
        ))}
        {state.isLoading && <li className="loading">Loading more...</li>}
      </ul>
    </div>
  );
}

↔️ Bi-Directional Form (The "Mage Pattern")

The complete, production-ready implementation for massive lists with memory recycling.

import React, { useRef, useLayoutEffect } from 'react';
import { useMageSelectController } from 'mage-select-data-react-hook-form';

function MassiveTagSelect({ control }) {
  const listRef = useRef<HTMLUListElement>(null);
  const anchorRef = useRef<{ id: string, offsetTop: number } | null>(null);

  const { state, loadMore, loadPrevious, toggleSelection } = useMageSelectController({
    name: 'tags',
    control,
    biDirectionalRechargeable: true, // Crucial for memory safety
    autoInitialLoad: true,
    fetchPage: (p) => api.tags.list(p),
    fetchByIds: (ids) => api.tags.getBatch(ids),
    getId: (t) => t.id
  });

  // 1. ANCHORING: Keeps items in place after top/bottom recycling
  useLayoutEffect(() => {
    if (anchorRef.current && listRef.current) {
      const node = listRef.current.querySelector(`[data-id="${anchorRef.current.id}"]`) as HTMLElement;
      if (node) {
        const diff = node.offsetTop - anchorRef.current.offsetTop;
        listRef.current.scrollTop += diff;
      }
      anchorRef.current = null;
    }
  }, [state.items]);

  const handleScroll = (e: React.UIEvent<HTMLUListElement>) => {
    const list = e.currentTarget;
    if (state.isLoading) return;

    // 2. TRIGGER: Capture visible anchor before loading next/prev page
    if (list.scrollTop <= 50 && state.hasPrevious) {
      const first = Array.from(list.children).find(c => (c as HTMLElement).offsetTop >= list.scrollTop);
      if (first) {
        anchorRef.current = { 
          id: (first as HTMLElement).dataset.id!, 
          offsetTop: (first as HTMLElement).offsetTop 
        };
      }
      loadPrevious();
    } else if (list.scrollHeight - list.scrollTop <= list.clientHeight + 50 && state.hasMore) {
      const first = Array.from(list.children).find(c => (c as HTMLElement).offsetTop >= list.scrollTop);
      if (first) {
        anchorRef.current = { 
          id: (first as HTMLElement).dataset.id!, 
          offsetTop: (first as HTMLElement).offsetTop 
        };
      }
      loadMore();
    }
  };

  return (
    <div className="mage-wrapper">
      <ul className="mage-list" ref={listRef} onScroll={handleScroll} style={{ height: '400px', overflow: 'auto' }}>
        {state.hasPrevious && <li className="loader">Loading previous...</li>}
        
        {state.items.map(tag => (
          <li 
            key={tag.id} 
            data-id={tag.id} // REQUIRED for anchoring logic
            onClick={() => toggleSelection(tag)}
            className={state.selectedItems.some(i => i.id === tag.id) ? 'selected' : ''}
          >
            {tag.name}
          </li>
        ))}

        {state.hasMore && <li className="loader">Loading more...</li>}
      </ul>
    </div>
  );
}

🛠 Controller Options

Extends all standard MageSelectEngineConfig properties plus:

| Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | name | string | Required | The RHF field name. | | control | Control | Required | The RHF control object. | | valueType | 'id' \| 'object' | 'id' | Determines if the form stores raw IDs or full objects. | | autoInitialLoad | boolean | false | Automatically triggers the first fetch on mount. | | defaultValue | T | undefined | Initial value for the field. |


Part of the Mage Select Ecosystem