React styles update only after wheel event fulfilled

What I want to achieve is smoothly scaled div container while scrolling (using mouse wheel to be strict) so user can zoom in and out.

However, my styles are "applied" by the browser only either when I scroll really slow or scroll normally and then wait about 0.2 seconds (after that time the changes are "bunched up"). I would like for the changes to be visible even during "fast" scrolling, not at the end.

The element with listener:

<div onWheel={(event) => {
         console.log("wheeling"); // this console log fires frequently, 
                                  // and I want to update styles at the same rate
    <div ref={scaledItem}> // content div that will be scaled according to event.deltaY
        ... // contents

My React code:

const changeZoom = useCallback((event: React.WheelEvent<HTMLDivElement>) => {
    if (!scaledItem.current) return;
    const newZoom = parseFloat( + event.deltaY * 0.001;
    console.log(newZoom); // logs as frequently as "wheeling" above
}, []);

useEffect(() => {
    if (!scaledItem.current) return; = currentZoom.toString();
}, [currentZoom]);

useEffect(() => {        // this is just for reproduction, needs to set initial scale to 1
    if (!scaledItem.current) return; = "1";
}, [])

What I have tried first was to omit all the React states, and edit directly from useCallback, but the changes took place in a bunch, after the wheeling events stopped coming. Then I moved zoom amount to currentZoom useState hook, but rerenders don’t help either.

I have also tried adding EventListener inside useEffect directly to the DOM Node:

useEffect(() => {
    if (!scaledItemWrapper.current) return; // ref for wrapper of my scaled content
    const container = scaledItemWrapper.current;
    container.addEventListener("wheel", changeZoom);
    return () => {
        container.removeEventListener("wheel", changeZoom);
}, [changeZoom]);

>Solution :

Instead of setting up multiple states and observing can you try using a single state below is a working example. Try this if this works

export default () => {
  const [pos, setPos] = useState({ x: 0, y: 0, scale: 1 });

  const changeZoom = (e) => {
    const delta = e.deltaY * -0.01;
    const newScale = pos.scale + delta;

    const ratio = 1 - newScale / pos.scale;

      scale: newScale,
      x: pos.x + (e.clientX - pos.x) * ratio,
      y: pos.y + (e.clientY - pos.y) * ratio

  return (
    <div onWheelCapture={changeZoom}>
          transformOrigin: "0 0",
          transform: `translate(${pos.x}px, ${pos.y}px) scale(${pos.scale})`

Leave a Reply