deck.gl: [Bug] React 18 compatibility

Description

I’ve been working with deck.gl + react-map-gl + React 17 for one year, almost every days, and I always used <DeckGL /> as a stateless component, following the doc : https://deck.gl/docs/developer-guide/interactivity#externally-manage-view-state. Geolocation and camera orientation are controlled from React, viewState is set with React useState() or Jotai atom.

Everything worked fine until I tried to use the exact same code with React 18. When the state is controlled with viewState + onViewStateChange() weird things happen. But using <DeckGL /> with the initialViewState works fine and smoothly.

For some time I thought it worked fine when I removed react-map-gl <Map /> but I was wrong, the bug was there without rendering it. It’s easier to check this in Safari. Rendering <Map/> seems to amplify the problem.

I spent some time reading issues and documentations from deck.gl and react-map-gl, and I did a lot of searches on google, but I found nothing about this problem, and nothing that helped me solve it.

Observations with React 18 defaults

  • in chromium-based browsers, scrolling to zoom in/out is possible but sometimes the movement ends with bounces. It looks like spring animation.
  • in Safari, scrolling to zoom in/out is almost impossible, the camera moves a little and comes back almost at the starting point.
  • in Firefox it’s more complicated, from yesterday until this morning it was like Safari but it’s now almost perfect. 😵 The only rational explanation I can think of would be an an auto-update of the browser I would have missed.

I tried to do some testing on browserstack but the rendering is too slow to have a good feedback, sorry.

Attempts to solve the problem

After 2 days looking for the origin of the problem, automatic batching in React 18 could be responsible of this. I’m not in my confort zone at all, so I could be totally wrong.

I tried to dig for deeper understanding, and I discovered the flushSync() function from react-dom to disable batch rendering. I wrapped setViewState()with it in onViewStateChange(), it’s better, deck.gl layer is zoomed smoothly but deck.gl and react-map-gl are desynchronized, basemap update is lagging behind. And there is a warning from React saying : Warning: flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task.

Doing more research on microtasks I tried to wrap the setViewState() in queueMicrotask() or requestAnimationFrame() instead of flushSync(). The result was better with requestAnimationFrame() but it’s not a solution : it’s laggy, some frames are sometimes dropped, but scrolling is possible in all browsers, bounces are gone in chrome and deck.gl layer stay synchronized with react-map-gl basemap. That is about as far as I can go by myself.

I hope to have forgotten nothing and I wish I’m sufficiently clear on this problem, english is not my native language.

I take this opportunity to thank you for the amazing vis.gl libraries and all your hard work !

Flavors

  • React
  • Python/Jupyter notebook
  • MapboxLayer
  • GoogleMapsOverlay
  • CartoLayer
  • DeckLayer/DeckRenderer for ArcGIS

Expected Behavior

Zoom in/out should be smooth, without bounces, like it was with React 17.

Steps to Reproduce

https://codesandbox.io/s/deck-gl-v8-8-react-v18-mwbyru?file=/src/App.js Fullscreen : https://mwbyru.csb.app/ (I think I saw more problems in fullscreen than in editor view)

A <select /> allows to use different “render modes”:

  • default
  • setState() wrapped in flushSync()
  • setState() wrapped in requestAnimationFrame()

A checkbox allows to enable/disable the react-map-gl basemap.

Environment

  • Framework version: deck.gl@8.8.4
  • Other libraries :react-map-gl@7.0.17 + react@18.2 + maplibre-gl@2.1.9
  • Browser: Chrome Version 103.0.5060.114 (Build officiel) (x86_64), Safari Version 15.5 (16613.2.7.1.9, 16613), Firefox 102.0.1 (64 bits)
  • OS: MacOS Big Sur 11.6.6

Logs

No response

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 7
  • Comments: 18 (1 by maintainers)

Most upvoted comments

I do notice touch pad zooming performance is much better on React 18 when mounting the app createRoot instead of render. However when I use createRoot, the canvas layers will flicker on window resize or canvas width resize.

I didn’t have any time to work on this problem until now. Thanks for the different answers, sadly nothing helped me solve this problem. I updated my mac to macOs 13 Venturi, but the problem is always there with Safari 16.1.

Today I tried to better understand how the “wheel” event was handled, because my last try showed a clear difference between mouse wheel event and trackpad wheel event on Safari. The mouse wheel worked, whereas the trackpad event reacted like a spring animation and the zoom came back to its initial value. Logging viewState showed that zoom values often came back to the exact same value after a bounce, and, sometimes, changed a little allowing a small zoom.

I tried different settings in the scrollZoom property of the controller prop : different speeds and smooth : true, but no miracle here. I just noticed it was worse with smooth : true.

After diving into codebase, I found the _onWheel function in controller.ts : https://github.com/visgl/deck.gl/blob/83ce5c11db4f577b93677ca960e95c27985631d8/modules/core/src/controllers/controller.ts#L530 In this function there is a call to _getTransitionProps(line 553). As the problem looked like a spring animation I tried to find a way to avoid the transition. The CommonViewState type equals TransitionProps, so I tried to set transitionDuration: 0 in the viewState object and then… the zoom worked smoothly with the trackpad wheel event ! 🤩 It seems to be the best result in all my tests.

I updated the sandbox with this last solution : https://codesandbox.io/s/deck-gl-v8-8-react-v18-mwbyru?file=/src/App.js

https://user-images.githubusercontent.com/16524008/204373886-90188ecc-9ba6-456d-84dc-c96d5c3c7877.mp4

I fear some possible side effects I’m not aware of, and I wonder if there is a better solution : a way to set this transition to 0 by default ? or only during ‘wheel’ event ? @Pessimistress I’d love to have advices on this subject.