...

/

Dynamically Replacing Components Based on User Actions

Dynamically Replacing Components Based on User Actions

Learn to add dynamic fields in our form using React.

Detecting elements using plain JavaScript

To detect events in plain JavaScript, we’d add the onchange attribute in line 7 to our select element, setting its value to the JavaScript code we’d like to execute. This is exactly how it works in React as well, except that we use the attribute onChange (note the capitalized “C”):

Press + to interact
import React from 'react'
class PayTypeSelector extends React.Component {
render() {
return (
<div className="field">
<label htmlFor="order_pay_type">Pay type</label>
<select onChange={this.onPayTypeSelected} name="order[pay_type]">
<option value="">Select a payment method</option>
<option value="Check">Check</option>
<option value="Credit card">Credit card</option>
<option value="Purchase order">Purchase order</option>
</select>
</div>
);
}
}

Note that we aren’t quoting the value to onChange but are instead using curly braces. This is another feature of JSX and is part of making the view dynamic. Curly braces allow us to interpolate JavaScript, much like how #{...} does in Ruby or <%= ... %> does in ERB. React knows to put quotes in the right places when the HTML is rendered.

Using React component

We can now define the method onPayTypeSelected() in line 3, like so:

Press + to interact
import React from 'react'
class PayTypeSelector extends React.Component {
onPayTypeSelected(event) {
console.log(event.target.value);
}

This implementation demonstrates how we can access the user’s selection. The event passed in is a synthetic event, which has a property target that is a DOMEventTarget. This in turn has a property value that has the value of the selected payment type.

If we run the following application, open the JavaScript console, and select different payment types, we should see messages in the console.

#---
# Excerpted from "Agile Web Development with Rails 6",
# published by The Pragmatic Bookshelf.
# Copyrights apply to this code. It may not be used to create training material,
# courses, books, articles, and the like. Contact us if you are in doubt.
# We make no guarantees that this code is fit for any purpose.
# Visit http://www.pragmaticprogrammer.com/titles/rails6 for more book information.
#---
class OrdersController < ApplicationController
  include CurrentCart
  before_action :set_cart, only: [:new, :create]
  before_action :ensure_cart_isnt_empty, only: :new
  before_action :set_order, only: [:show, :edit, :update, :destroy]

  # GET /orders
  # GET /orders.json
  def index
    @orders = Order.all
  end

  # GET /orders/1
  # GET /orders/1.json
  def show
  end

  # GET /orders/new
  def new
    @order = Order.new
  end

  # GET /orders/1/edit
  def edit
  end

  # POST /orders
  # POST /orders.json
  def create
    @order = Order.new(order_params)
    @order.add_line_items_from_cart(@cart)

    respond_to do |format|
      if @order.save
        Cart.destroy(session[:cart_id])
        session[:cart_id] = nil
        format.html { redirect_to store_index_url, notice: 
          'Thank you for your order.' }
        format.json { render :show, status: :created,
          location: @order }
      else
        format.html { render :new }
        format.json { render json: @order.errors,
          status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /orders/1
  # PATCH/PUT /orders/1.json
  def update
    respond_to do |format|
      if @order.update(order_params)
        format.html { redirect_to @order, notice: 'Order was successfully updated.' }
        format.json { render :show, status: :ok, location: @order }
      else
        format.html { render :edit }
        format.json { render json: @order.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /orders/1
  # DELETE /orders/1.json
  def destroy
    @order.destroy
    respond_to do |format|
      format.html { redirect_to orders_url, notice: 'Order was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_order
      @order = Order.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def order_params
      params.require(:order).permit(:name, :address, :email, :pay_type)
    end
  #...

  private
     def ensure_cart_isnt_empty
       if @cart.line_items.empty?
         redirect_to store_index_url, notice: 'Your cart is empty'
       end
     end

      
    def pay_type_params
      if order_params[:pay_type] == "Credit card"
        params.require(:order).permit(:credit_card_number, :expiration_date)
      elsif order_params[:pay_type] == "Check"
        params.require(:order).permit(:routing_number, :account_number)
      elsif order_params[:pay_type] == "Purchase order"
        params.require(:order).permit(:po_number)
      else
        {}
      end
    end

end
A running application to play with

What do we do with this new method? If you recall, a React component is a view and state. When the state changes, the view is rerendered by calling the component’s render() method. We want the view to be rerendered when the user changes payment types, so we need to get the currently selected payment type into the component’s state.

We can do this ...