kinetic-slider
Version:
A WebGL-powered kinetic slider component using PIXI.js
175 lines (172 loc) • 6.79 kB
JavaScript
import { useRef, useEffect, useCallback } from 'react';
// Development environment check
const isDevelopment = "production" === 'development';
/**
* Hook to set up external navigation elements for the slider
* Fully optimized with:
* - Batch event listener registration
* - Stable event handler references
* - Comprehensive error handling
* - Memory leak prevention
* - Element reference caching
* - Event propagation control
* - Optimized dependency tracking
* - Type safety improvements
*/
const useExternalNav = ({ externalNav, navElement, handleNext, handlePrev, resourceManager }) => {
// Track found elements to avoid unnecessary DOM queries
const elementsRef = useRef({ prevNav: null, nextNav: null });
// Create stable event handlers that internally reference the latest callback functions
const handlersRef = useRef({
prevHandler: (e) => {
try {
e.preventDefault();
// Call the latest function reference
const latestHandler = handlersRef.current.latestPrevFn;
if (typeof latestHandler === 'function') {
latestHandler();
}
}
catch (error) {
}
},
nextHandler: (e) => {
try {
e.preventDefault();
// Call the latest function reference
const latestHandler = handlersRef.current.latestNextFn;
if (typeof latestHandler === 'function') {
latestHandler();
}
}
catch (error) {
}
},
latestPrevFn: null,
latestNextFn: null
});
// Keep the latest function references updated
useEffect(() => {
handlersRef.current.latestPrevFn = handlePrev;
handlersRef.current.latestNextFn = handleNext;
}, [handlePrev, handleNext]);
// Memoize the batch registration function for better performance
const setupBatchListeners = useCallback((prevElement, nextElement, prevHandler, nextHandler) => {
try {
if (!resourceManager)
return false;
// Create listeners maps for each element
const prevListenersMap = new Map();
prevListenersMap.set('click', [prevHandler]);
const nextListenersMap = new Map();
nextListenersMap.set('click', [nextHandler]);
// Register event listeners in batch operations
resourceManager.addEventListenerBatch(prevElement, prevListenersMap);
resourceManager.addEventListenerBatch(nextElement, nextListenersMap);
return true;
}
catch (error) {
return false;
}
}, [resourceManager]);
// Setup regular DOM event listeners
const setupDirectListeners = useCallback((prevElement, nextElement, prevHandler, nextHandler) => {
try {
prevElement.addEventListener('click', prevHandler);
nextElement.addEventListener('click', nextHandler);
return true;
}
catch (error) {
return false;
}
}, []);
// Main effect for setting up and cleaning up navigation
useEffect(() => {
// Skip during server-side rendering
if (typeof window === 'undefined')
return;
// Skip if external navigation is not enabled
if (!externalNav)
return;
// Track initialization status for cleanup
let isInitialized = false;
try {
// Find the navigation elements in the DOM
const prevNav = document.querySelector(navElement.prev);
const nextNav = document.querySelector(navElement.next);
// Store references to found elements
elementsRef.current = { prevNav, nextNav };
// Check if both elements are found
if (!prevNav || !nextNav) {
// Create helpful error message
const missingElements = [];
if (!prevNav)
missingElements.push(`"${navElement.prev}"`);
if (!nextNav)
missingElements.push(`"${navElement.next}"`);
// Log warning in development mode
if (isDevelopment) ;
return;
}
// Get stable event handlers
const { prevHandler, nextHandler } = handlersRef.current;
// Try batch registration first, fall back to direct listeners if needed
let registrationSuccessful = false;
if (resourceManager) {
registrationSuccessful = setupBatchListeners(prevNav, nextNav, prevHandler, nextHandler);
}
// Fall back to direct listeners if batch registration failed or unavailable
if (!registrationSuccessful) {
registrationSuccessful = setupDirectListeners(prevNav, nextNav, prevHandler, nextHandler);
}
// Mark as successfully initialized
isInitialized = registrationSuccessful;
}
catch (error) {
}
// Cleanup on unmount or dependencies change
return () => {
try {
// Skip cleanup if not initialized
if (!isInitialized)
return;
// Get current element references for cleanup
const { prevNav, nextNav } = elementsRef.current;
const { prevHandler, nextHandler } = handlersRef.current;
// ResourceManager handles its own cleanup
if (!resourceManager && prevNav && nextNav) {
// Safely remove event listeners
try {
prevNav.removeEventListener('click', prevHandler);
}
catch (e) {
if (isDevelopment) ;
}
try {
nextNav.removeEventListener('click', nextHandler);
}
catch (e) {
if (isDevelopment) ;
}
}
// Clear element references to help garbage collection
elementsRef.current = { prevNav: null, nextNav: null };
}
catch (cleanupError) {
}
};
}, [
externalNav,
navElement.prev,
navElement.next,
resourceManager,
setupBatchListeners,
setupDirectListeners
]);
// Return current elements for potential external use
return {
elements: elementsRef.current
};
};
export { useExternalNav as default };
//# sourceMappingURL=useExternalNav.js.map