Skip to content

Bug: url_params override form_params in ReflexData#params (3.5.x) #753

@mbaertschi

Description

@mbaertschi

Summary

In StimulusReflex 3.5.x, ReflexData#params merges params in the wrong order, causing URL query-string params (from the original page load) to override serialized form data (what the user just submitted). This silently discards user input for any field whose name also appears in the page URL.

Root cause

lib/stimulus_reflex/reflex_data.rb:

def params
  form_params.deep_merge(url_params)  # url_params win — WRONG
end

deep_merge gives precedence to the argument, so url_params (stale, from the initial GET request) overwrite form_params (current user input). The correct order is:

def params
  url_params.deep_merge(form_params)  # form_params win — CORRECT
end

How to reproduce

  1. Load a page via GET with array params in the URL, e.g. ?items[1][tags][]= (empty — this is the hidden Rails input that ensures the field is always submitted even when nothing is selected).
  2. The user selects a value in a <select multiple> on the form.
  3. A reflex button with data-reflex-serialize-form="true" fires.
  4. In the reflex, params[:items]["1"][:tags] is [""] (from the URL) instead of the user's selection.

Impact

  • Any reflex that reads params for fields that appear in the page URL will see the original page-load value, not the user's current input.
  • Multi-selects and checkboxes are especially affected because Rails always emits a hidden field[]= with an empty value, which ends up in the URL and then stomps the real selection on the next reflex call.
  • The bug is invisible unless server-side code inspects the affected field — making it very easy to miss in testing.

Migration context

In SR 3.4.x, combined dataset mode automatically serialized the form and the result was merged as form_data.deep_merge(data["params"]) — no URL params were involved at all. When upgrading to 3.5.x and replacing combined with ancestors + data-reflex-serialize-form="true", the new URL-param inclusion silently breaks the contract that existed in 3.4.x.

Workaround (until fixed)

Monkey-patch ReflexData in an initializer:

module StimulusReflexFormParamsFix
  def params
    url_params.deep_merge(form_params)
  end
end
StimulusReflex::ReflexData.prepend(StimulusReflexFormParamsFix)

Environment

  • stimulus_reflex gem: 3.5.5
  • stimulus_reflex npm: 3.5.5
  • Rails: 8.0
  • Ruby: 3.4.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions