thoughtbot / superglue

A productive library for Classic Rails, React and Redux
https://thoughtbot.github.io/superglue/
MIT License
359 stars 10 forks source link

Form helpers for Superglue #21

Closed jho406 closed 1 year ago

jho406 commented 2 years ago

Rail's form helpers are an incredible way to quickly build forms from backend objects. I'd like to have some parity on the superglue side so quickly build uncontrolled components on the React side without using RailsTag

Something like this:

json.form_props form_with_props(
  url: onboarding_income_index_path,
  model: current_user.profile,
  method: :post,
  scope: :profile, local: true) do |f|

  f.number_field :email, max_length: 5
  f.number_field :password
  f.number_field :password_confirmation
end
   {
     .....
     email: {
       input: { type: "email",  maxLength: 5}
      }
   }

then on the Javascript side:

<form {...formProps}>
  <label {...formProps.email.label} />
  <input {...formProps.email.input}/>

  <label {...formProps.password.label}>
  <input {...formProps.password.input}>

  <RailsForm.Field {...formProps.passwordConfirmation} />

  <input type="submit" />
</RailsForm>

We leave how to structure the form up to the user so they can do this if they want in only html:

<label {...formProps.email.label}>
  <input {...formProps.email.input}/>
</label>
stevepolitodesign commented 2 years ago

@jho406 here's where I left off:

require "active_support/core_ext/string"
require "rspec"

# TODO: form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
def form_props(url: nil, scope: nil, **options)
  builder = MyFormBuilder.new(url: url, scope: scope, **options)
  yield builder

  builder.to_h
end

class MyFormBuilder
  attr_reader :scope

  def initialize(url: nil, scope: nil, **options)
    @scope = scope
    @output = {
      accept_charset: "UTF-8",
      action: url || "/",
      method: options [:method] || "post",
      elements: {}
    }
  end

  def text_field(method, options = {})
    @output[:elements][scope ? "#{scope}_#{method}".to_sym : method.to_s.to_sym] = {
      type: "text",
      label: method.to_s.humanize,
      name: scope ? "#{scope}[#{method}]" : method.to_s
    }.merge(options)
  end

  def email_field(method, options = {})
    @output[:elements][scope ? "#{scope}_#{method}".to_sym : method.to_s.to_sym] = {
      type: "email",
      label: method.to_s.humanize,
      name: scope ? "#{scope}[#{method}]" : method.to_s
    }.merge(options)
  end

  def to_h
    @output
  end
end

RSpec.describe "form props" do
  it "builds default attributes for the form" do
    props = form_props {}

    expect(props).to eq(
      accept_charset: "UTF-8",
      action: "/",
      method: "post",
      elements: {}
    )
  end

  it "sets attributes for the form" do
    props = form_props(url: "/some_url", method: "get") {}

    expect(props).to eq(
      accept_charset: "UTF-8",
      action: "/some_url",
      method: "get",
      elements: {}
    )
  end

  it "builds attributes for the form elements" do
    props = form_props do |my_form_builder|
      my_form_builder.text_field :first_name, min: 50
      my_form_builder.email_field :email, required: true, label: "Email address"
    end

    expect(props[:elements]).to eq(
      first_name: {
        type: "text",
        min: 50,
        label: "First name",
        name: "first_name"
      },
      email: {
        type: "email",
        required: true,
        label: "Email address",
        name: "email"
      }
    )
  end

  it "scopes form elements" do
    props = form_props(scope: "user") do |my_form_builder|
      my_form_builder.text_field :first_name
      my_form_builder.email_field :email
    end

    expect(props[:elements]).to eq(
      user_first_name: {
        type: "text",
        label: "First name",
        name: "user[first_name]"
      },
      user_email: {
        type: "email",
        label: "Email",
        name: "user[email]"
      }
    )
  end
end
jho406 commented 1 year ago

Closing this as https://github.com/thoughtbot/form_props has been created! 🥳