npm install react-ref-store
# or
pnpm add react-ref-store
# or
yarn add react-ref-store- When parent components need to access DOM elements of child components
- When you want to manage DOM in a React-friendly way without using querySelector
- When you need type-safe access to different DOM element types
- Examples: tabs, menus, animation indicators, etc.
A factory function that creates Context, Provider, and Hook all at once with type safety.
// Define your refs type
type TabRefs = {
'tab-button': HTMLButtonElement;
'tab-panel': HTMLDivElement;
};
const TabRefsStore = createRefsStore<TabRefs>();
// Returns
{
Provider, // Context Provider component
useStore, // Hook to get the store (must be used within Provider)
}Creates a store that manages refs as a Map data structure with type safety. (Can be used standalone without Context)
type SearchRefs = {
'search-field': HTMLDivElement;
'search-input': HTMLInputElement;
};
function MyComponent() {
const refsStore = useRefsStore<SearchRefs>();
// Use Map API: refsStore.get(), refsStore.has(), etc.
}A hook that registers a DOM element's ref to the Store with type safety.
const ref = useRegisterRef(refsStore, 'unique-key');
return <div ref={ref}>...</div>;// 1. Define your refs type
type TabRefs = {
'tab-button': HTMLButtonElement;
'tab-panel': HTMLDivElement;
};
// 2. Create Store
const TabRefsStore = createRefsStore<TabRefs>();
// 3. Wrap with Provider
export function TabGroup({ children }) {
return <TabRefsStore.Provider>{children}</TabRefsStore.Provider>;
}
// 4. Register in child components
function Tab({ id, children }) {
const store = TabRefsStore.useStore();
const ref = useRegisterRef(store, id); // TypeScript infers correct element type
return <button ref={ref}>{children}</button>;
}
// 5. Use the Store
function TabIndicator({ activeTabId }) {
const store = TabRefsStore.useStore();
const activeTab = store.get(activeTabId); // Returns HTMLButtonElement | undefined
if (!activeTab) return null;
const rect = activeTab.getBoundingClientRect();
// Calculate position and render indicator...
}// You can inject an external store into the Provider
function CustomTabGroup({ children, externalStore }) {
return (
<TabRefsStore.Provider refsStore={externalStore}>
{children}
</TabRefsStore.Provider>
);
}When using standalone without Context:
type FormRefs = {
'submit-btn': HTMLButtonElement;
'email-input': HTMLInputElement;
};
function StandaloneComponent() {
const refsStore = useRefsStore<FormRefs>();
// Use Map API with type safety
const buttonRef = refsStore.get('submit-btn'); // HTMLButtonElement | undefined
const hasInput = refsStore.has('email-input'); // boolean
return <ChildComponent refsStore={refsStore} />;
}// Different element types for different keys
type ComplexRefs = {
'header': HTMLHeaderElement;
'nav': HTMLNavElement;
'main-content': HTMLMainElement;
'footer': HTMLFooterElement;
'submit-button': HTMLButtonElement;
'search-input': HTMLInputElement;
};
const LayoutStore = createRefsStore<ComplexRefs>();
function Header() {
const store = LayoutStore.useStore();
const ref = useRegisterRef(store, 'header'); // RefObject<HTMLHeaderElement>
return <header ref={ref}>Header</header>;
}
function SearchBar() {
const store = LayoutStore.useStore();
const ref = useRegisterRef(store, 'search-input'); // RefObject<HTMLInputElement>
return <input ref={ref} type="search" />;
}interface RefsMap<T extends Record<string, HTMLElement>> {
register<K extends keyof T>(key: K, element: T[K] | undefined): void; // Register element
unregister<K extends keyof T>(key: K): void; // Unregister element
get<K extends keyof T>(key: K): T[K] | undefined; // Get element
has<K extends keyof T>(key: K): boolean; // Check if element exists
clear(): void; // Remove all elements
}interface ProviderProps<T extends Record<string, HTMLElement>> {
children: ReactNode;
refsStore?: RefsMap<T>; // Optional external store injection
}interface UseRegisterRefOptions {
isDefer?: boolean; // Defer registration using requestAnimationFrame (default: true)
isEnabled?: boolean; // Enable/disable registration (default: true)
}- When Context is needed: Use
createRefsStore<T>() - For local usage without Context: Use
useRefsStore<T>() - Use only inside Provider:
useStore() - Need external store control: Pass
refsStoreprop to Provider
- Key validation: TypeScript ensures you only use valid keys
- Element type inference: Each key maps to the correct DOM element type
- Method type safety: All store methods are properly typed
- Ref type safety:
useRegisterRefreturns the correct ref type for each key
MIT
Contributions are always welcome! Please read the contribution guidelines first.
If you find a bug, please create an issue here.
