Home > Net >  How to add an event handlers to JSX node by event name?
How to add an event handlers to JSX node by event name?

Time:10-09

I have an usual situation where a React component receives an event name and a handler function as props, and I must attach this function an an event handler for said event in the element returned by this component:

const Component(eventName, handler) => {
   return <div>{...}</div>
}

My initial idea was to create a props object and spread on the component:

const Component(eventName, handler) => {

   const props = { ['on' eventName]: handler };
   
   return <div {...props}>
       {...}
   </div>
}

The problem is that the event name comes in lower case, such as click, dragend, etc

And React expect the camel case syntax instead.

So if I receive 'click' string as prop, it has to turn into an onClick key on the props object. This is not as simple as capitalizing the event and prepending 'on' to it, as some events have multiple words. For example, dragend needs to turn into onDragEnd for React to be happy.

Does anyone has suggestions how can I accomplish this without maintaining a huge list of event names -> jsx properties mapped in this component?

CodePudding user response:

Are the event handler functions expecting that when they are called, they will be handed a browser event object, or a react synthetic event object?

If they are expecting browser events (which seems likely since they are using the browser event naming convention), then it might make sense to register all the events with an effect - see below.

Note that I've done the work to memoize the event prop object. This isn't strictly necessary for functionality, but it would help with performance if this event object changed identity frequently in your component's parent:

import React, { useEffect, useRef } from "react";
import isDeepEqual from "fast-deep-equal/react";

const testEvents = {
  click: () => console.log("click event handler fired!"),
  drag: () => console.log("click event handler fired!"),
  mouseOver: () => console.log("click event handler fired!")
};

const TestComponent = ({ events }) => {
  const ref = useRef(null);
  const memoizedEvents = useMemoizedEvents(events);
  useEffect(() => {
    const currentRef = ref.current;
    Object.entries(memoizedEvents).forEach(([name, handler]) =>
      currentRef.addEventListener(name, handler)
    );
    // This function cleans up old event handlers.
    // It will run whenever memoziedEvents changes before the new handlers are registered.
    return () =>
      Object.entries(memoizedEvents).forEach(([name, handler]) =>
        currentRef.removeEventListener(name, handler)
      );
  }, [memoizedEvents]);
  return (
    <div ref={ref}>
      DOM Element with Event Handlers
    </div>
  );
};

/**
 * This hook makes sure that the "events" object only changes identity if any of the things within it have changed.
 * This way, the effect that registers/unregisters the click handlers won't run unnecessarily.
 */
function useMemoizedEvents(events) {
  const eventsRef = useRef();
  if (!isDeepEqual(eventsRef.current, events)) {
    eventsRef.current = events;
  }
  return eventsRef.current;
}

const App = () => <TestComponent events={testEvents} />;

See this CodeSandbox

  • Related