JavaScript form fetch request submitting empty values when they are filled in

Viewed 376

I am creating a Login form in JavaScript and am trying to send this form to my Ruby on Rails backend.

I have two problems:

  1. When the form loads, it triggers the eventListener and sends the blank form's data to the backend. I have e.preventDefault() commented out in the second function below because I kept getting the error that it is not a function. Note: in the backend for Rails, I get the following message when I type in params. "Permitted: false" concerns me. <ActionController::Parameters {"controller"=>"sessions", "action"=>"create", "session"=>{}} permitted: false>

  2. When I fill in the form with an email and password and click the submit button, the loginData (from loginButton.addEventListener("submit", submitLogin(loginData) submits a blank value for the email and 'password' for the password (which are the default values I set to test the values) even though these elements are filled in in the form with an actual email address and password.

Function loading login form (note: this loads just fine):

  // create the elements
  var div = document.createElement("div"),
    log = document.createElement("div"),
    loginForm = document.createElement("form"),

  //set form attributes
  loginForm.setAttribute("method", "POST");

  // set body styles
  document.body.style.textTransform = "capitalize";

  log.id = "login";
  log.innerHTML = "login";

  // set loginForm styles
  loginForm.id = "loginForm";

  // set the elements and styles on the form
  loginForm.innerHTML =
    "<label>username</label><br/>" +
    "<input type='text' id='login-email' value='' placeholder='email' style='" +
    inputStyles +
    "' /><br/>" +
    "<label>password</label><br/>" +
    "<input type='password' id='login-password' value='value' placeholder='*************' style='" +
    inputStyles +
    "' /><br/>" +
    "<input type='submit' id='login-button' value='Login' style='" +
    btnStyles +
    "' />" +
    "<p><a style='" +
    forgetStyles +
    "' href='#'>forget password ?</a></p><br/>";
 
  // append the buttons and form on main-div
  div.appendChild(log);
  div.appendChild(loginForm);

  // append main-div on the body
  document.body.appendChild(div);

  //get login form values to submit
  let loginEmail = document.getElementById("login-email").value;
  let loginPassword = document.getElementById("login-password").value;
  let loginData = { member: { loginEmail, loginPassword } };
}

SubmitLogin Function (fetch request to Rails backend):

async function submitLogin(e) {
  // e.preventDefault();
  const loginData = new FormData(e.target);
  debugger;
  let options = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Access-Control-Allow-Origin":
        "file:///Users/awb/Coding/Flatiron/Projects/bookclub-javascript-rails-api/bookclub-frontend-javascript/index.html",
      "Access-Control-Allow-Methods": "POST",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
      Accept: "application/json",
    },
    body: JSON.stringify(loginData),
  };

  fetch("http://localhost:3000/login", options)
    .then((resp) => {
      resp.json();
    })
    .then((member) => {
      console.log(member);
      return new Member(member);
    });
}

If it matters, this is the order of my scripts at the bottom of my index.html page:


    <script src="src/suggestion.js"></script>
    <script src="src/member.js"></script>
    <script src="src/gathering.js"></script>
    <script src="src/book_group.js"></script>
    <script src="src/book.js"></script>
    <script src="src/login_registration_form.js"></script>
    <script src="src/index.js"></script>

Where index.js calls the function "loadRegistrationLogin()" as the last line on index.js

My routes are:

Rails.application.routes.draw do
  resources :members, only [:create, :index, :show]
  resources :registrations, only: [:create]
  resources :sessions, only: [:create]
  
  post '/login', to: 'sessions#create'
  delete '/logout', to: 'sessions#destroy'
  get '/logged_in', to: 'sessions#is_logged_in?'

  root to: "static#home"
  
end

and finally, my session controller:

class SessionsController < ApplicationController
    include CurrentMemberConcern
    skip_before_action :verify_authenticity_token

    def create
        member = Member
        .find_by(email: params[:member][:email])
        .try(:authenticate, params[:member][:password])

        if member 
            login!
            render json: {
                status: :created, 
                logged_in: true,
                member: member 
            }
        else 
            render json: { 
                status: 401,
             errors: ['No such member', 'Verify credentials and try again or sign up']
            }
        end
    end
end
1 Answers

Your form is submitting automatically because of the way you've set the event handler. The .addEventListener() API requires a reference to a callback function as the second argument. But you passed a function call with arguments like this:

loginButton.addEventListener("submit", submitLogin(loginData));

You have two choices to fix this:

  1. Pass a reference.
    • This will require loginData to be available to the handler in some other fashion.

loginButton.addEventListener("submit", submitLogin);

  1. Enclose the call within a function expression:

loginButton.addEventListener("submit", ()=>submitLogin(loginData));

Option 2 is generally preferred when needing to pass parameters to the handler. But you'll see below, for you, option one is the way to go.

The leads to the next problem - submitLogin() function itself.

async function submitLogin(e) {
  // e.preventDefault();
  const loginData = new FormData(e.target);

In your eventListener setup you went out of your way to pass a parameter to this function - yet it isn't used. You correctly attempt to create a new FormData() Object. But interestingly enough you pass e.target, which is the button element. That won't work Reference.

To fix that change:

const loginData = new FormData(e.target);

to: (assuming only one form on the page)

const loginData = new FormData(document.forms[0]);

But we aren't done yet. Using e.preventDefault() is unnecessary if you set up your button correctly. A button with type="submit" is designed to submit the form to the server. That's also true for a button with no type attribute at all. You clearly don't want to submit the form, so don't put a submit button on it.

Change the login button from:

type="submit"

To:

type="button"

Now you can remove the e parameter from your function.

All of that should get you a lot closer to success.

Related