Back

Introduction to the upcoming useEvent hook RFC

Introduction to the upcoming useEvent hook RFC

To my knowledge, no two keys unlock the same lock. If I want to use my sibling’s iPhone without knowing their PIN, I must have the same eyes or fingerprints as them, which is impossible. You must find the best fit for a problem. React solves problems by using the best hook for the job; if the hook hasn’t been created, they create one; that’s why we have useEvent() to talk about.

This tutorial will answer why useEvent() was created, how it’s expected to be used, and the problems it will solve. It is vital to remember that the useEvent Hook is presently at RFC level, being considered by the React community, and not currently ready for use.

Why was the Hook Proposed?

Consider developing an Amazon clone as an example. It is assumed that you will have a section where your chosen purchases should go, aptly named Shopping-Cart. You will also require a Purchase-button component to confirm your purchases so that payments can be made. Purchase-Button is the child component, and Shopping-Cart is the parent component. When the former is clicked, we want to call an onPurchase function in the latter.

Currently, the child component will also be rendered each time the parent component is updated. This can result in enormous performance loss afterward: imagine the purchase button has probably five more levels of components, all of which will also be re-rendered.

This happens because of referential identity, also known as equality. The characteristic of objects that sets them apart from other objects is their identity. In its literal sense, the reference identity is nothing more than the use of the object reference as identity. Simply said, reference identity is memory address equality, where two variables can point to the same content (such as a post office box) or not. References are unmanaged because they are hidden pointers.

In React, it’s common practice for components to automatically re-render anytime their properties change, which is why the child component will re-render: the referential identity of the onPurchase method has changed.

Implementing the useEvent() Hook

UseEvent() was developed to address the issue of the sporadic change in referential identity brought on by every render that forces children to re-render. The hook will have the following characteristics:

  • UseEvent() enables you to define an event handler with an always stable function identity. In other words, each time a page is refreshed, the event handler will be the same.
  • UseEvent lets no new instance of the function be created if a prop or state changes.
  • UseEvent() makes available to the function the most recent value for both prop and state.
  • UseEvent() aids cleaner code and enables smooth readabilty.

The basic implementation of the useEvent() hook is:

function MyComponent() {
  const [State, setState] = useState(0);
  const manageEvent = useEvent(() => {
    console.log('see state: `, State);
  });
  }

//useEvent() Hook guarantees that there is only one instance of a function; no dependencies are required.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

Real-time example: Cons of optimizing functions and components

Consider that there is a search bar and a search button. The user types text into the Search Bar, which the Search Bar then transmits to the button using the onClick method.

Although this is what we want to happen for the bar, the fact that Search Bar is dependent on the props—in this example, which is the text, it means that the component will be re-loaded each time the props change. However, we don’t want the handleClick function to be rebuilt on each re-load.

const searchField= ({text}) =>{
    const handleClick=()=>{
        search(text)
    }
    return(
        <div>
            <inputs value={text}/>
            <Button onClick={handleClick}
        </div>
    )
}

Since we want the input to update whenever the text does, input re-loading would be desirable in this case, but it is not required to recreate handleClick or re-render the Button. UseEvent() assists by enabling handleClick to always refer to the same function regardless of text update:

const searchField= ({text}) =>{
    const handleClick = useEvent(() =>{
        search(text)
    });
    return(
        <div>
            <inputs value={text}/>
            <Button onClick={handleClick}
        </div>
    )
}

When event handlers are changed, it triggers useEffect re-fire

UseEffect is a fantastic approach to monitor state changes and trigger event handlers accordingly, but occasionally it fires more frequently than we anticipate.

One of these scenarios is when we have a list of dependencies and event handlers inside useEffect that are called whenever any dependencies change, as opposed to when we want some of them to be called just when specific dependencies change.

The following illustration below is an approximate implementation of useEvent. The Chat component’s effect is linked to the chosen room in this example. It displays a toast with the chosen theme when you enter the room or get a message and, depending on the muted option, may play a sound:

function Chat({ selectedRoom }) {
  const [muted, setMuted] = useState(false);
  const theme = useContext(ThemeContext);

  useEffect(() => {
    const socket = createSocket('/chat/' + selectedRoom);
    socket.on('connected', async () => {
      await checkConnection(selectedRoom);
      showToast(theme, 'Connected to ' + selectedRoom);
    });
    socket.on('message', (message) => {
      showToast(theme, 'New message: ' + message);
      if (!muted) {
        playSound();
      }
    });
    socket.connect();
    return () => socket.dispose();
  }, [selectedRoom, theme, muted]); // 🟡 Re-runs when any of them change
  // ...
}

This approach has the drawback of making the socket reconnect when the theme is changed, or the volume is turned down. This is because theme and muted are used within the effect and must be listed in the effect dependency list. The effect must be re-run when they change, destroying and creating the socket.

Here, we need to establish a connection and send toasts based on selectedRoom, theme, and muted variables. However, we don’t want the socket to reconnect when theme or muted changes only on selectedRoom. However, React will issue a warning if we omit theme and muted from the list of dependencies because they are utilized inside the effect.

The following is how useEvent() handles this kind of issue:

from RFC:
function Chat({ selectedRoom }) {
  const [muted, setMuted] = useState(false);
  const theme = useContext(ThemeContext);

  // ✅ Stable identity
  const onConnected = useEvent(connectedRoom => {
    showToast(theme, 'Connected to ' + connectedRoom);
  });

  // ✅ Stable identity
  const onMessage = useEvent(message => {
    showToast(theme, 'New message: ' + message);
    if (!muted) {
      playSound();
    }
  });

  useEffect(() => {
    const socket = createSocket('/chat/' + selectedRoom);
    socket.on('connected', async () => {
      await checkConnection(selectedRoom);
      onConnected(selectedRoom);
    });
    socket.on('message', onMessage);
    socket.connect();
    return () => socket.disconnect();
  }, [selectedRoom]); // ✅ Re-runs only when the room changes
}

This way, onConnected and onMesssage won’t get re-created every time useEffect() gets fired. They will get the most recent value of message and connectedRoom, which enables us not to include theme and muted in the dependency list because if they are not included, socket connection won’t be created on every muted and theme change.

When is the useEvent Hook Inappropriate to Use?

UseEvent Hook shouldn’t be used in certain circumstances. Let’s examine the when and the why.

The when: Using functions made with the useEvent Hook during rendering is not permitted.

The why: Because the code will crash.

function MyComponent() { 
  const listOfData = useEvent(() => {
    //Return some data
    return ['marvel', 'openreplay', 'useevent'];
  });
  
  return 
  <div>
    {listOfData().map(item => <li>{item}</li>}
  </div>;
}

Conclusion

Although useEvent() isn’t yet usable, it is something React developers should keep an eye on and is undoubtedly a promising development. The useEvent() Hook’s logic was examined in this article, along with the situations in which you should and shouldn’t use it.

A TIP FROM THE EDITOR: For more on hooks for React, don’t miss our Understanding The UseId Hook In React and A Guide To The React UseState Hook articles.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs and track user frustrations. Get complete visibility into your frontend with OpenReplay, the most advanced open-source session replay tool for developers.

OpenReplay