Overview
Follow up from https://git.drupalcode.org/project/canvas/-/merge_requests/960#note_806852
When making changes to multivalue props, a separate request is made for each prop items. e.g. if we reorder a 3 items' list, we do 3 separate request debounced. This is O(N) in terms of requests made and we cannot ensure there won't be collisions, their order, and the preview might be stale if a race-condition happens. Fixing this would not only avoid race conditions, but also reduce the number of requests Drupal needs to handle (O(1)).
Proposed resolution
Debounces the requests per component instance form instead of per input
Issue fork canvas-3586848
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
justafishComment #5
shubham.prakash commentedAI Assisted Code:
Summary:
Root cause: Each InputBehaviorsComponentPropsForm instance created its own debounce(formStateToStore, DEBOUNCE_TIMEOUT) via useRef. When a multivalue field triggers N simultaneous input changes (e.g. reordering 3 items fires 3 _weight field updates), each of the N component instances had its own independent debounce timer — so N requests were sent instead of 1.
Fix: A module-level Map (perComponentDebouncedSenders) holds one shared debounced function per component instance form. All InputBehaviorsComponentPropsForm instances for the same component UUID share that single entry.
On mount, each instance increments the ref-count and creates the shared entry if it doesn't exist yet.
On unmount, it decrements the ref-count and only cancels + deletes the entry when the last input for that component unmounts — preventing premature cancellation of a pending call that other still-mounted inputs might be relying on.
commitFormState now routes through perComponentDebouncedSenders.get(selectedComponent) instead of the per-instance debounce, so all N simultaneous changes collapse into one request.
The per-instance debounceFormStateToStore is kept only as a fallback for the non-shared path (scalar props on code components that bypass debouncing).
Comment #6
penyaskitoWe already had this: https://git.drupalcode.org/project/canvas/-/merge_requests/960/diffs?com....
Both implementations should be compared.