Dynamically Replacing Components Based on User Actions
Learn to add dynamic fields in our form using React.
We'll cover the following...
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”):
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:
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
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 via the method ...