React hooks & the closure hell
Tags: react, reacthooks, javascript, frontend
Published: 2019-06-24 14:59:36

---
title: React hooks & the closure hell
published: true
description: Since event handlers are closures we need to either re-create them on each render or whenever one of it's dependencies changes. This means components that only depend on the event handler (and possibly not on the handler's dependencies) will have to re-render too.
tags: react, reacthooks, javascript, frontend
---
#React hooks & the closure hell

Since Facebook introduced functional components and hooks, event handlers become simple closures. Don't get me wrong, I like functional components, but there is a number of issues that niggle at me, and when I ask about them in the community, the most common answer is: "*don't worry about premature optimizations*".

But that is the problem for me, I grew up programming in C, and I frequently worry about the performance of my applications, even if others find it less significant.

## The problem?
Since event handlers are closures we need to either re-create them on each render or whenever one of it's dependencies changes. This means components that only depend on the event handler (and possibly not on the handler's dependencies) will have to re-render too.

Consider this example code ([Try here](https://codesandbox.io/s/react-hooks-usestate-m59ve)):
```js
import React, { useState, useCallback, memo } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

let times = 0

const ExpensiveComponent = memo(({ onClick }) => (
  <p onClick={onClick}>I am expensive form component: {times++}</p>
))

const App = () => {
  const [value, setValue] = useState(1);

  const handleClick = useCallback(
    () => {
      setValue(value + 1)
    },
    [value],
  );

  return (
    <div className="app">
      <ExpensiveComponent onClick={handleClick} />
      <button onClick={handleClick}>
        I will trigger expensive re-render
      </button>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

In the previous example, clicking on the button will cause `ExpensiveComponent` to re-render. In case of class based components it would be unnecessary.

## Solution?
The experimental tinkerer I am I tried to find the solution to this problem, solution where we can use functional components, but don't have to create a new callback every time we create a new value.

So I created `useBetterCallback(fn, deps)`. The signature for this function/hook is identical to `useCallback(fn, deps)`. The difference is that it will always return the same identical handler no matter what.

Some of you might think: '*So how do I access fresh state values?*'. `useBetterCallback` will call your handler with one additional argument, and that argument is an array with all dependencies your callback depends on. So instead of recreating the callback we pass new values to existing one.

Here is the source code for the `useBetterCallback` hook.
```js
const useBetterCallback = (callback, values) => {
  const self = useRef({
    values: values,
    handler: (...args) => {
      return callback(...args, self.current.values)
    }
  });
  self.current.values = values
  return self.current.handler
}
```

And here is an example of the `useBetterCallback` in action ([Try here](https://codesandbox.io/s/react-hooks-usestate-e1207)):
```js
import React, { useState, useRef, memo } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const useBetterCallback = (callback, values) => {
  const self = useRef({
    values: values,
    handler: (...args) => {
      return callback(...args, self.current.values)
    }
  });
  self.current.values = values
  return self.current.handler
}

let times = 0

const ExpensiveComponent = memo(({ onClick }) => (
  <p onClick={onClick}>I am expensive form component: {times++}</p>
))

const App = () => {
  const [value, setValue] = useState(1);

  const handleClick = useBetterCallback((event, [ value, setValue ]) => {
    setValue( value + 1 )
  }, [value, setValue])

  console.log("Value: " + value)

  return (
    <div className="app">
      <ExpensiveComponent onClick={handleClick} />
      <button onClick={handleClick}>
        I will not trigger expensive re-render
      </button>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
```

## Review?
What do you think?