'Code with event handler using "functional state update" working perfectly in React 17 does not work properly in React 18
I have observed a very strange behavior of React 18 compared to React 17 and I am wondering what I am doing wrong (since React 18 was not supposed to "break" existing code except for the small required adjustments in index.tsx (or index.js, respectively) regarding the import statement of ReactDOM and the main render function).
I would like to zoom a div with Ctrl + scroll wheel where the same "point" stays underneath my mouse arrow. Therefore, I need to translate my div while zooming.
The following code works perfectly in React 17. In React 18 the "functional state update" (setParams((params) => {...}) does not work properly. params.zoom gets updated, however, params.translateX and params.translateY do not get updated. Is this maybe related to "state batching"? What can I do to solve this issue?
Of course, I could move the event handler inside of my useEffect hook, but then I would have to create dependencies of params (like [divElement, params]) which I would like to avoid for performance reasons since my event handler would get removed/re-added everytime the state of params changes.
Here is the relevant code:
Interface for scale for zooming ("zoom") and 2D translation of zoomed div element:
interface Parameters {
zoom: number;
translateX: number;
translateY: number;
}
useState hook for params:
const [params, setParams] = useState<Parameters>({
zoom: 1,
translateX: 0,
translateY: 0,
});
useRef hook for my div:
const divElement: React.LegacyRef<HTMLDivElement> = useRef(null);
Event handler for my "wheel event" with functional (!) update of the state of params:
const onWheel = useCallback((event: WheelEvent) => {
if (event.ctrlKey) {
event.preventDefault();
console.log('offsetX: ' + event.offsetX + ', offsetY: ' + event.offsetY);
setParams((params) => {
const newZoom: number = Math.min(
Math.max(params.zoom + event.deltaY * 0.001, 0.2),
1
);
const newTranslateX: number =
params.translateX - event.offsetX * (newZoom - params.zoom);
const newTranslateY: number =
params.translateY - event.offsetY * (newZoom - params.zoom);
return {
zoom: newZoom,
translateX: newTranslateX,
translateY: newTranslateY,
};
});
}
}, []);
useEffect hook that registers my div as active event listener for "wheel event":
useEffect(() => {
const currentElement: HTMLDivElement | null = divElement.current;
if (currentElement) {
currentElement.addEventListener('wheel', onWheel, { passive: false });
return () => {
currentElement.removeEventListener('wheel', onWheel);
};
}
}, [divElement, onWheel]);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
