concur: Loading 1100 clickEl's is very slow
I have a list of 1100 users and I tried to make a button for each one (using clickEl) that returned the userId to a parent element, which I would use to jump to a new route. But it’s extremely slow loading that many click elements. It could handle a couple hundred well, but approaching 1000 the performance really plummets. Once they load up, it’s pretty responsive, but loading them take at least 10 seconds.
For now I just made a fake button function that uses a href instead of Concur’s events machinery:
linkButton :: String -> String -> Widget HTML ()
linkButton label url = el_ E.a [ A.href . JS.pack $ url ]
$ el E.button [ A.class_ "pure-button" ] [ text label ]
which is really fast and fine for now since I’m just jumping to an URL, but at some point I’m sure it would be nice/essential to be able to use clickEl’s or some other event listener for a big list.
About this issue
- Original URL
- State: open
- Created 7 years ago
- Comments: 20 (14 by maintainers)
Commits related to this issue
- Add clickList benchmark [https://github.com/ajnsit/concur/issues/6] — committed to ajnsit/concur-benchmarks by ajnsit 7 years ago
Hah well, it’s really a new feature, and I’m glad it solved your problem! The changes are all here - https://github.com/ajnsit/concur/commit/78ac2b224d1d2454ad4ddb014bc48950cc46481a.
Gist
Basically I added support for blocking IO to Concur. Then used it for a very common use case where async io was a bottleneck.
Background
All actions like
liftIOandliftSTMare run async, and are effectively full widgets.A common pattern with a lot of widgets (like
button) was to useliftSTMto procure aNotify(a very quick operation), and then used the Notify to link the view click and the handler. This means that a singleButtonwas effectively a sequence of two widgets,liftSTMand then the actual button UI.Also all widgets are encapsulated and run async, and any changes to the widget tree causes a full dom render cycle (albeit with virtual-dom diffing).
Problem
So when a
buttonis first added to the widget tree, it actually renders theliftSTMwidget (i.e. no UI), which quickly finishes, and then renders the actual button UI.When you add two
buttons in quick succession, the firstliftSTMwidget renders and finishes, then the secondliftSTMwidget renders and finishes, and then the firstbuttonUI renders, and then the secondbuttonUI renders. The order can vary depending on how long it takes for aliftSTMto finish.You see where this is going.
With 1100
buttonwidgets, you have 1100 quick dom rerender cycles in a row which takes ~10 seconds on your machine, including the overhead for all the rapid widget sequencing.Solution
I added a special function called
awaitViewActionwhich provides aNotifyto a widget, but does so with blocking IO. Blocking IO prevents DOM updates so cannot be used for long running IO actions, and hence I am reluctant to make it available to clients. However this specific use case of constructing aNotifyin STM is guaranteed to be extremely short and can safely be exposed to client widgets.With this change, on adding 1100
buttonwidgets, the IO actions are quickly run in succession without touching the DOM, and finally each widget constructs its own UI which is populated into the DOM at once (one render total for all 1100 buttons). The same as what happens with your “fake”linkButtoncode.Okay I’m pretty certain I understand why this is happening. Will push a fix soon-ish.