React hooks, hammerjs with closures

Viewed 858

In a feature request, I needed to detect swipe events and change something on dom accordingly. So I decided to use hammerjs and give it a shot for react hooks. But I didn't understand how the closures actually work in code below. Can someone explain why left & right handlers are always using first value of state object

tl;dr: Can't change counter properly with swipes. Goes only to -1 or 1 min max

const App = () => {
  const elm = useRef(null);
  const [counter, setCounter] = useState(0);
  const [obj, setObj] = useState({ counter: 0 });
  const [mounted, setMounted] = React.useState(false);

  React.useLayoutEffect(() => {
    if (!mounted && elm.current) {
      const h = new Hammer(elm.current);
      h.on("swipeleft", () => setCounter(counter + 1));
      h.on("swiperight", () => setCounter(counter - 1));
      h.on("swipeleft", () => setObj({ counter: obj.counter + 1 }));
      h.on("swiperight", () => setObj({ counter: obj.counter - 1 }));
      setMounted(true);
    }
  });

  return (
    <div
      ref={elm}
      style={{ width: "300px", height: "300px", backgroundColor: "orange" }}
    >
      <p>This is a swipeable content area</p>
      <p>Counter: {counter}</p>
      <p>Obj counter: {obj.counter}</p>
    </div>
  );
};

Here is codesandbox https://codesandbox.io/s/l911vj98yz

1 Answers

The scope of the callback for the Hammer object is only captured once at the first render. It doesn't matter if the value change, the scope is preserved with the initial value. To avoid this issue you can use the functional form to update your values.

h.on("swipeleft", () => setCounter(previousCounter => previousCounter + 1);
Related