deck.gl
deck.gl copied to clipboard
[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. :dizzy_face: 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
- [X] 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: [email protected]
- Other libraries :[email protected] + [email protected] + [email protected]
- 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
Thanks for this write-up. I've been experiencing the same thing after upgrading to react 18 and the latest deckgl, and thought I'd broken my code somewhere. I couldn't figure out how to fix the zooming issue without removing the underlying StaticMap
I was using... 🙂
Appreciate most contributors will be working on this in their spare time, so of course I don't expect any solution ASAP, but I would like to throw my support behind getting this fixed whenever possible!
I am having problems reliably reproducing this issue. The linked Sandbox works fine on my machine (Mac Mini Intel) in any browser. I have seen in the past that you can get synchronization issues sometimes but not others because React uses some internal logic to batch updates.
Any suggestions on how I can make the problem more pronounced?
@Pessimistress When I saw the sandbox worked fine on your Mac Mini I thought that my outdated version of Safari could be a part of the problem. I updated my MBP : I was under MacOS BigSur, I'm now under MacOS Monterey and Safari version is now Version 15.6.1 (17613.3.9.1.16). Sadly, nothing changed.
Then I realized that the Mac Mini doesn't have a trackpad, and the problem occurs only on scroll with trackpad to zoom in/out. So I tried to zoom with a mouse instead, and the mouse wheel works a lot better than the trackpad ! 😮 With the trackpad there is a spring effect (back and forth), so it's really hard to zoom in or out. With the mousewheel there is no spring effect.
I'll post a comparative video of the problem as soon as possible.
Thanks for this write-up. I've been experiencing the same thing after upgrading to react 18 and the latest deckgl, and thought I'd broken my code somewhere. I couldn't figure out how to fix the zooming issue without removing the underlying
StaticMap
I was using... 🙂Appreciate most contributors will be working on this in their spare time, so of course I don't expect any solution ASAP, but I would like to throw my support behind getting this fixed whenever possible!
Thank you for your reply, it's good to to know it's not an isolated case. It could help to find the source of the problem. 🤞
What OS are you running, and did you encounter the problem with the trackpad or the mouse ?
Recorded on MacBook Pro (16 pouces, 2019), MacOS Monterey 12.5.1, Safari 15.6.1.
Zoom in with trackpad : https://user-images.githubusercontent.com/16524008/187234496-bc6305f8-5a47-4c5d-a97e-963a9e8ead0c.mp4
Zoom in with mouse : https://user-images.githubusercontent.com/16524008/187234416-5dcf1070-0f4c-4814-a85c-8c40a04dbedb.mp4
I am pretty new to Deck.gl, so I don't know if this will be helpful, but there is an Upgrade Guide that might address some of the issues you are running into.
Was having similar issue when upgrading to react 18. For me it was mainly for using lodash throttle. If i remove that then it seems to work nicely. Somehow flusing solved the issue as well but received different error on the console. Tried to generate in sandbox from above: https://codesandbox.io/embed/deck-gl-v8-8-react-v18-forked-ek46si?fontsize=14&hidenavigation=1&theme=dark
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.
We are also seeing the flashing @athomann reports locally in https://github.com/vitessce/vitessce
@imhaage hi) Do you have any updates here?
I'm sorry @artem-malko, I have no update about this problem. I have been working on a lot of different things since my last comment, far away from frontend world, and I'm just returning on our frontend codebase now.
@Pessimistress, I still hope you can help us with this problem. :pray:
For now I think we are going to update to React 18, we really need to update our stack to take advantage of the last versions of our libraries. We'll try to use the (hacky :cry:) solution I found in november. I hope it will not introduce more problems.
@imhaage Ok)
By hacky solution you mean https://github.com/visgl/deck.gl/issues/7158#issuecomment-1329722960, don't u?)
@imhaage Ok)
By hacky solution you mean #7158 (comment), don't u?)
Exactly, it doesn't seem to me to be the best way to fix the problem. Maybe I'm overthinking.
Ok, thx)
So it still hasn't been solved? It's worse on mobile
We were fighting with the issue too. One thing which we found helpful was localizing the viewport to the React component which renders the map and debouncing the propagation of changes to the global app state. Something like this:
import {useDebounce} from 'react-use';
…
const [localViewState, setLocalViewState] = useState<MapViewport>(globalViewState);
useDebounce(setGlobalViewState, 100, [localViewState]);
return (
<DeckGL initialViewState={localViewState} onViewStateChange={setLocalViewState} … />
)
@ilyabo Did you get any side effects?) How smoothly is it?