使用 ReactJS 向 Rails 后端提交表单
Submit a form to Rails backend with ReactJS
这是我第一次在 Rails 解决方案中使用 ReactJS,任务是紧急请求,没有给我足够的时间在开始冲刺之前学习 React。后端是一个 JSON (API) 解决方案,我已经能够编写一个 ReactJS 来呈现 GET 端点以列出数据库中的任务。但是,当我到了为 POST 端点提交带有 ReactJS 的表单时,什么也没有发生。我的输入没有保存到数据库中,并且表单没有消失到任务列表端点。我不知道我做错了什么。但是,下面是我的解决方案的代码片段。
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
这是空文件
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>;
当我在 Postman 和 Insomnia 上调用 API 端点时,它们按预期运行。但是当我从 ReactJS 表单提交输入时(即 NewTask.jsx),没有任何内容保存到数据库中。
我做错了什么。我希望有人能帮助我解决这个问题。
显然,问题在于 onSubmit 事件方法,因为它针对 avatar_url 而不是名为 name
的表单输入属性。所以这就是我用来让它工作的方法。
将 onChange 事件更改为:
onChange(event) {
this.setState({ [event.target.name]: event.target.value });
}
将表单输入属性更正为:
<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>
这是我第一次在 Rails 解决方案中使用 ReactJS,任务是紧急请求,没有给我足够的时间在开始冲刺之前学习 React。后端是一个 JSON (API) 解决方案,我已经能够编写一个 ReactJS 来呈现 GET 端点以列出数据库中的任务。但是,当我到了为 POST 端点提交带有 ReactJS 的表单时,什么也没有发生。我的输入没有保存到数据库中,并且表单没有消失到任务列表端点。我不知道我做错了什么。但是,下面是我的解决方案的代码片段。
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
这是空文件
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>;
当我在 Postman 和 Insomnia 上调用 API 端点时,它们按预期运行。但是当我从 ReactJS 表单提交输入时(即 NewTask.jsx),没有任何内容保存到数据库中。
我做错了什么。我希望有人能帮助我解决这个问题。
显然,问题在于 onSubmit 事件方法,因为它针对 avatar_url 而不是名为 name
的表单输入属性。所以这就是我用来让它工作的方法。
将 onChange 事件更改为:
onChange(event) { this.setState({ [event.target.name]: event.target.value }); }
将表单输入属性更正为:
<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>