# The useEvent Hook for React

Dan Abramov has [opened an RFC](https://github.com/reactjs/rfcs/pull/220) proposing [the addition](https://github.com/facebook/react/pull/24505) of a `useEvent` hook to React. It's meant to allow for this change in your codebase:

```diff
- const onClick = useCallback(() => console.log("Hello", name), [name]);
+ const onClick = useEvent(() => console.log("Hello", name));
```

# Performance Optimization

When you're running into performance issues, you'll eventually discover the higher-order component `memo` (which is quite similar to the pre-hooks base class `PureComponent`). By default React will always re-render the whole subtree of a component. If one of the child components is wrapped in `memo` though, that component will (by default) perform a shallow equality check of its props, and prevent React from traversing the tree further if they didn't change.

Since this relies on shallow equality checks, you'll start using `useCallback` a lot:

```js
function MyComponent({ name }) {
  const onClick = useCallback(() => {
    console.log("Hello", name);
  }, [name]);

  return <MyMemoButton onClick={onClick} />;
}

const MyMemoButton = memo(...);
```

Note how you have to pass `name` as a dependency here. If you didn't, then clicking the button would render a stale value: the **first** name that was passed to the component.

This feels very crufty and annoying, especially as the dependency list grows.

# Habitual useCallback

Especially when you're working on a large application with many people, the churn when you eventually have to add `memo` to one component is high. You'll have to find and change all the places where props are passed to it to use `useMemo` or `useCallback`. If contexts are involved, it's even harder. It's also dangerously easy to cause a performance regression because someone passed another prop to your component and didn't make the effort to use `useMemo`.

The habit of using `useCallback` and `useMemo` means less churn, less regressions, less performance surprises, and less mental overhead while writing and reviewing code. You can even make this conventional, and enforce it to some extent with the [jsx-no-bind](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md) ESLint rule in your codebase (which by default will not `allowArrowFunctions`).

## Good Habit or Bad Habit

There are arguments to [avoid `useCallback` and `useMemo`](https://kentcdodds.com/blog/usememo-and-usecallback) and only do this optimization once your app becomes slow.

The core arguments are that using them

1. makes code harder to write and read,
    
2. adds constant baseline cost (time and memory), and
    
3. adds variable memory cost (by unnecessarily holding references).
    

While (1) is absolutely true, (2) should be in the realm of nanoseconds and bytes, while (3) requires a bit more context:

When returning large, transformed objects from `useMemo` this is true. However in that case the calculation of that object was also probably expensive. So holding onto is also more likely to be preferable. When using `useCallback` for event handlers, they will be referenced by the DOM anyway.

If we can fix the developer experience of (1), then I'd happily accept (2) and (3) if I get a scalable application in exchange. Furthermore, it's not just about performance but also about side-effects. We'll get to that in a second.

# Addressing the Developer Experience

If you have been using React for a while, you might remember that before we had hooks, this felt easier:

```js
class MyComponent extends React.PureComponent {
  handleClick() {
    console.log("Hello", this.props.name);
  }
  
  render() {
    return <MyPureButton onClick={this.handleClick} />;
  }
}
```

With `useEvent` we get this experience back!

```js
function MyComponent({ name }) {
  const onClick = useEvent(() => {
    console.log("Hello", name);
  });

  return <MyMemoButton onClick={onClick} />;
}
```

# Side-Effects

Your codebase might have side-effects, often around data loading.

```js
useEffect(() => {
  setOtherParties([]);
  api.listParties(date)
    .then(parties => parties.filter(b => b.host !== user))
    .then(setOtherParties);
}, [date, user, setOtherParties]);
```

Note how this effect triggers whenever the `date` or the `user` changes (for example when changing accounts). Extracting a predicate naively would lead to issues. Since `filter` is not stable, it would constantly cause the effect:

```js
const filter = (party) => party.host !== user;

useEffect(() => {
  setOtherParties([]);
  api.listParties(date)
    .then(parties => parties.filter(filter))
    .then(setOtherParties);
}, [date, filter, setOtherParties]);
```

The `exhaustive-deps` ESLint rule (which does more than its name suggests) would call out that issue and encourage you to fix this by wrapping it in `useCallback`, like this:

```python
const filter = useCallback((party) => party.host !== name, [name]);
```

If the name changes, the reference will change, and will cause the effect when intended. However, the lint rule **can not check this across component boundaries**!

## Predicates, Setters & Loaders

In the situation above, `useEvent` would probably be wrong and a bug.

```js
const filter = useEvent((party) => party.host !== name);
```

If you were to do the following, a change of the name would not cause the effect.

## Effects & Events

If we look at a different example, something is wrong:

```python
const greet = useCallback((name) => {
  console.log(lang == "es" ? "Hola" : "Hello", name);
}, [lang]);

useEffect(() => {
  greet(user.name);
}, [user]);
```

When the `user` changes, then a new greeting is emitted. But when the locale changes, the user is greeted again.

```python
const greet = useCallback((name) => {
  console.log(lang == "es" ? "Hola" : "Hello", name);
}, [lang]);

useEffect(() => {
  greet(user.name);
}, [user]);
```

In this case, `useEvent` will give us the desired behavior.

# But isn't this exactly like…

It might seem that `useEvent` might just be the same as some other hooks that we already have, but it's not.

```python
// stable, but stale name on second invocation
const greet = useRef((name) => console.log(name)).current;

// stable, but stale name on second invocation
const greet = useCallback((name) => console.log(name), []);

// not stable
// useLatest coming from a library like react-use
const greet = useLatest((name) => console.log(name));
```

# Closing Notes

So when do I use which? My rough guideline is that if a callback is named `on...`, `handle...`, `dispatch...`, `set...` and has no return value, it's a good candidate for `useEvent`. Otherwise there's a good chance it's more of a predicate or loader, in which case `useCallback` is the preferred choice still.

It would be great if the ESLint `exhaustive-deps` ESLint rule could treat return values of `useEvent` like `useRef` and know that they are stable, so that one doesn't need to include them in dependencies further down. I had to fork the ESLint plugin to make it do so.

As I went through our codebase, I found that I was able to change more than 90% of our `useCallback` to `useEvent`, reducing quite a bit of cruft. A great change!
