@equinor/fusion-react-utils
Version:
Helper and util functions for React
72 lines • 3.51 kB
JavaScript
import { useLayoutEffect, useMemo, useRef } from 'react';
import { createSyntheticEvent } from '../create-synthetic-event';
import { shallowEqual } from '../shallow-equal';
const noEvents = {};
/**
* Add event listeners to a native element
* @param ref React ref object of native element
* @param eventHandlers handler to attach to element, must match map (normally props of component)
* @param eventMap Map of events where key is prop name and value is event name
*/
export const useElementEvents = (ref, eventHandlers, eventMap = {}) => {
/** keep a reference of all registered events */
const listenersRef = useRef({});
/** keep a reference of all registered event handler */
const handlersRef = useRef({});
/** memorize provided event handlers provided by component properties */
const handlers = useMemo(() => {
/** early exit, no props provided */
if (!eventHandlers)
return noEvents;
/** extract properties that are defined as custom event handlers */
const handlers = Object.keys(eventMap)
.filter((k) => k in eventHandlers)
.reduce((cur, key) => Object.assign(cur, { [key]: eventHandlers[key] }), {});
/** compare event handlers with existing */
const hasChanged = !shallowEqual(handlersRef.current, handlers);
/** only return if event handlers has changes */
return hasChanged ? handlers : handlersRef.current;
}, [handlersRef, eventMap, eventHandlers]);
/**
* Check for changes in event handlers as an side effect
* No teardown since registered event listeners create synthetic react events that access
* the referenced callback. We also assume that when this component, the event target is also
* unmounted and do not need teardown.
*/
useLayoutEffect(() => {
if (!handlers)
return;
const listeners = listenersRef.current;
const propKeys = Object.keys(eventMap).filter((k) => k in handlers);
/** register all new events */
for (const propKey of propKeys) {
if (!(propKey in listeners)) {
/** internal callback that generate synthetic event that reference provided callback */
const eventListener = (e) => {
const handler = handlersRef.current[propKey];
handler && handler(createSyntheticEvent(e));
};
console.debug(`adding event listener [${propKey}]`, ref.current);
/** register listener and add to index */
ref.current?.addEventListener(eventMap[propKey], eventListener);
Object.assign(listeners, { [propKey]: eventListener });
}
}
/** generate list over events removed in props */
const removed = Object.keys(listeners).filter((k) => !(k in handlers));
for (const key of removed) {
console.debug(`removing event listener [${key}]`, ref.current);
const type = eventMap[key];
const handler = listeners[key];
if (type) {
handler && ref.current?.removeEventListener(type, handler);
delete listeners[key];
}
}
/** assign indexes to reference */
listenersRef.current = listeners;
handlersRef.current = handlers;
}, [eventMap, ref, handlersRef, listenersRef, handlers]);
};
export default useElementEvents;
//# sourceMappingURL=useElementEvents.js.map