It is my first time working with ReactJS in a Rails solution and the task is an emergency request that doesn’t give me enough time to learn React before taking up the sprint to work on. The Backend is a JSON (API) solution and I have been able to write a ReactJS to render the GET endpoint to list the Tasks in the database. However, when I got to the point of submitting a form with ReactJS for a POST endpoint, nothing happened. My inputs do not save into the Database and the form did not disappear to the Task list endpoint. I do not know what I am doing wrong. However, below is code snippets to my solution.
controller/api/v1/tasks_controller.rb
# frozen_string_literal: true module Api module V1 class TasksController < ApiController include StrongParameters def index @task = Task.all.load render json: @task end def create @task = Task.create!(create_action_params) if @task render json: @task else render json: @task.errors end end private def create_action_params params.require(:task).permit(permitted_task_attributes) end end end end
config/route.rb
# frozen_string_literal: true Rails.application.routes.draw do # For React root 'homepage#index' get '/new_task' => 'homepage#index' # For API Backend namespace :api do namespace :v1 do get 'tasks/index' post 'tasks/create' end end end
model/task.rb
# frozen_string_literal: true class Task < ApplicationRecord validates :avatar_url, presence: true validates :description, presence: true end
views/homepage/index.htnl.erb
This is empty file
views/layout/application.html.erb
<!DOCTYPE html> <html> <head> <title>Title</title> <meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no"> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'Index' %> </head> <body> <%= yield %> </body> </html>
app/javascript/components/App.jsx
import React from "react"; import Routes from "../routes/Index"; export default props => <>{Routes}</>;
app/javascript/components/NewTask.jsx
import React from "react"; import {Link} from "react-router-dom"; class NewTask extends React.Component { constructor(props) { super(props); this.state = { avatar_url: props.post.avatar_url, description: props.post.description }; this.onChange = this.onChange.bind(this); this.onSubmit = this.onSubmit.bind(this); this.stripHtmlEntities = this.stripHtmlEntities.bind(this); } stripHtmlEntities(str) { return String(str) .replace(/</g, "<") .replace(/>/g, ">"); } onChange(event) { this.setState({ [event.target.avatar_url]: event.target.value }); } onSubmit(event) { event.preventDefault(); const url = "api/v1/tasks/create"; const { avatar_url, description } = this.state; if (avatar_url.length === 0 || description.length === 0) return; const body = { avatar_url, description: description.replace(/n/g, "<br> <br>") }; const token = document.querySelector('meta[name="csrf-token"]').content; fetch(url, { method: "POST", headers: { "X-CSRF-Token": token, "Content-Type": "application/json" }, body: JSON.stringify(body) }) .then(response => { if (response.ok) { return response.json(); } throw new Error("Network response was not ok."); }) .then(response => this.props.history.push(`/new_task/${response.id}`)) .catch(error => console.log(error.message)); } render() { return ( <> <nav className="navbar navbar-expand-lg navbar-dark task-navbar-color"> <div className="container"> <div className="navbar-header"> <p className="navbar-brand">Add Task</p> </div> <div> <ul className="nav navbar-nav navbar-right"> <li> <Link to="/"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-arrow-left text-white" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/> </svg> </Link> </li> </ul> </div> </div> </nav> <div className="container mt-5"> <div className="row"> <div className="col-sm-12 col-lg-6 offset-lg-3"> <h1 className="font-weight-normal mb-5"> Add a new task to our awesome task collection. </h1> <form onSubmit={this.onSubmit}> <div className="form-group"> <label htmlFor="taskAvatar">Avatar URL</label> <input type="url" name="task[avatar_url]" value={this.state.value} className="form-control" required onChange={this.onChange} /> </div> <label htmlFor="description">Task Description</label> <textarea className="form-control" name="task[description]" value={this.state.value} rows="5" required onChange={this.onChange} /> <button type="submit" value="Save" className="btn btn-primary mt-3"> Add Task </button> <Link to="/" className="btn btn-link mt-3"> Back to tasks </Link> </form> </div> </div> </div> </> ); } } export default NewTask;
app/javascript/packs/Index.jsx
import React from "react"; import { render } from "react-dom"; import 'bootstrap/dist/css/bootstrap.min.css'; import $ from 'jquery'; import Popper from 'popper.js'; import 'bootstrap/dist/js/bootstrap.bundle.min'; import App from "../components/App"; document.addEventListener("DOMContentLoaded", () => { render( <App />, document.body.appendChild(document.createElement("div")) ); });
app/javascript/routes/Index.jsx
import React from "react"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Task from "../components/Task"; import NewTask from "../components/NewTask"; export default <Router> <Switch> <Route path="/" exact component={Task} /> <Route path="/new_task" exact component={NewTask} /> </Switch> </Router>;
When I call the API Endpoints on Postman and Insomnia, they function as expected. But when I submit inputs from ReactJS Form (i.e. NewTask.jsx), nothing saves into the database.
What am I doing wrong. I do hope somebody can help me through this.
Advertisement
Answer
Apparently, the problem lies in the onSubmit event method because it targets the avatar_url instead of form inputs attributes called name
. So this is what I used to make it work.
Changed the onChange event to be:
onChange(event) { this.setState({ [event.target.name]: event.target.value }); }
Correct the Form Input Attributes, to be:
<form onSubmit={this.onSubmit}> <div className="form-group"> <label htmlFor="avatar_url">Avatar URL</label> <input type="url" name="avatar_url" value={this.state.value} className="form-control" required onChange={this.onChange} /> </div> <label htmlFor="description">Task Description</label> <textarea className="form-control" name="description" value={this.state.value} rows="5" required onChange={this.onChange} /> <button type="submit" value="Save" className="btn btn-primary mt-3"> Add Task </button> <Link to="/" className="btn btn-link mt-3"> Back to tasks </Link> </form>