fix: propagate changed options to a running supercluster worker - #1049
Conversation
useSuperclusterWorker kept optionsRef up to date on every options change, but only ever sent an 'init' message to the worker when workerUrl changed. Since the worker-init effect intentionally excludes options from its dependency array, changing radius/maxZoom/minPoints etc. after mount silently had no effect on an already-running worker. Track whether this is the first run of the options effect and, on subsequent changes, re-send 'init' with the new options to the worker. Re-initializing the worker's clusterer discards any previously loaded data, so also resend 'load' for the current geojson and request fresh clusters for the current viewport.
Adds a regression test suite for the stale-options bug fixed in the previous commit, using a MockWorker to capture postMessage calls. Covers: initial options sent on mount, changed options propagated to a running worker, previously loaded data reloaded after re-init, clusters re-requested for the current viewport, and no worker messages sent when the options reference is unchanged across rerenders.
|
Thanks for catching this. The bug is real and it is my fault: the options were stored but only ever read when the worker was first created, so later changes never reached it. One issue with the fix. The effect depends on useSuperclusterWorker(geojson, {radius: 60}, viewport, workerUrl);That object is new on every render, so React will treat it as changed every time. The worker would be re-initialised, all features reloaded, and clusters re-requested on every render. That would be worse than the original bug. The example app does not show this because it defines its options outside the component, so the reference stays the same. This repo already has a fix for this. Also, minor and not blocking: Nice tests. The one showing that re-init throws away loaded data is a good catch. |
|
First of all: thanks a lot for the fix and detailed observations! I am a bit perplexed at the moment.
It is too specific, introduces external dependencies, and is only tangentially related to the library itself. I'll need to investigate how that happened, but we'll probably have to move it to the examples where it belongs. |
|
Ok, so apparently I missed removing the files in ./src before merging @samithahansaka's PR (#891) in January and never noticed the files since then... |
|
Sorry about that, the duplicate in If it helps, the cleanup is smaller than it looks. It is a deletion rather than a move, because the example never used the library copies:
Both already exist under Happy to open that PR if you would rather not spend the time on it, or to leave it to you since you are already in there. Either is fine with me. On the fix itself, no rush on my comment above given the code is moving anyway. The reference equality point still applies once it lives in the examples, but it matters less there, since the example controls its own call site. |
|
You're right, this should be fine. I'll apply this fix to the files in the example, and we can just delete the files from Feel free to open a PR deleting the two files if you want. Otherwise, I'll just do it in this PR... |
|
Thanks @samithahansaka for the review and @usefulthink for the context. This makes sense that this belongs in the examples. @samithahansaka's point about reference equality is valid. If the fix gets applied to the examples copy, using @usefulthink Happy to update this PR to target the examples copy and include the src/ deletion, if that's easier than splitting the work. Either way works for me. Best regards. |
ecd75cb to
d92c1c6
Compare
Summary
useSuperclusterWorkersilently ignored changes to itsoptionsargument (radius,maxZoom,minPoints, etc.) after the initial mount, so a running worker kept clustering with whatever options were passed in on the very first render.Motivation
The worker-init effect only sends the
initmessage to the worker whenworkerUrlchanges, by design — it intentionally excludesoptionsfrom its dependency array so that unrelated re-renders don't tear down and recreate the worker.But nothing else ever pushed a changed
optionsvalue to an already-running worker:optionsRef.currentwas kept up to date, but that ref was only read once, at worker creation time.As a result, changing clustering options after mount had no observable effect, even though the ref itself was correct.
What I have done
src/hooks/use-supercluster-worker.tsto re-sendinit(plusloadandgetClusters, since re-init discards loaded data) to the running worker wheneveroptionschanges after mount, instead of only on the initial run.src/hooks/__tests__/use-supercluster-worker.test.tsxwith aMockWorkerto verify the worker receives these messages correctly.Test Plans
init/load/getClustersmessages after anoptionschange.jestsuite to check for regressions.eslintandtsc --noEmiton the changed files.Test Results