การสร้างฟอร์มใน React
บทความเกี่ยวกับพื้นฐาน React เรื่องการของจัดการ Form และ Input ต่างๆ เนื่องจากว่าการทำ Web Application ปฎิเสธไม่ได้ว่า เราต้องใช้พวก Input Form ต่างๆ ในการส่งข้อมูลไปที่ Server HTML
Element ประเภท ฟอร์ม (form) จะทำงานแตกต่างจาก DOM element ประเภทอื่นๆ ใน React เล็กน้อย เนื่องจากโดยปกติ element ประเภทฟอร์ม จะมีการเก็บ state ไว้เป็นของตัวเองอยู่แล้ว ในการควบคุมการส่งฟอร์ม วิธีมาตรฐานที่จะสามารถทำสิ่งที่กล่าวมานี้ได้ คือใช้เทคนิคที่เรียกว่า “คอนโทรลคอมโพเนนท์” (controlled components)
ข้อกำหนดเบื้องต้น
ข้อกำหนดสำหรับบทความนี้คือ คุณต้องปฏิบัติตามบทความ เชื่อมต่อ React กับ REST API มาก่อน
การสร้างฟอร์มใน React
ที่ไฟล์ App.js เขียนโค้ด ดังนี้
import React, { Component, Fragment } from 'react';
import {BrowserRouter as Router, Switch, Route, Link, useParams, useRouteMatch} from 'react-router-dom';
import Movies from './components/Movies';
import Admin from './components/Admin';
import Home from './components/Home';
import OneMovie from './components/OneMovie';
import Genres from './components/Genres';
import OneGenre from './components/OneGenre';
import EditMovie from './components/EditMovie';
export default function App() {
return (
<Router>
<div className="container">
<div className="row">
<h1 className="mt-3">
Go Watch a Movie!
</h1>
<hr className="mb-3"></hr>
</div>
<div className="row">
<div className="col-md-2">
<nav>
<ul className="list-group">
<li className="list-group-item">
<Link to="/">Home</Link>
</li>
<li className="list-group-item">
<Link to="/movies">Movies</Link>
</li>
<li className="list-group-item">
<Link to="/genres">Genres</Link>
</li>
<li className="list-group-item">
<Link to="/admin/add">Add movie</Link>
</li>
<li className="list-group-item">
<Link to="/admin">Manage Catalogue</Link>
</li>
</ul>
</nav>
</div>
<div className="col-md-10">
<Switch>
<Route path="/movies/:id" component={OneMovie} />
<Route path="/movies">
<Movies />
</Route>
<Route path="/genre/:id" component={OneGenre} />
<Route exact path="/genres">
<Genres />
</Route>
<Route path="/admin/add" component={EditMovie} />
<Route path="/admin">
<Admin />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</div>
</div>
</Router>
);
}
ภายในโฟลเดอร์ components สร้างไฟล์คอมโพเนนท์ ชื่อ EditMovie.js มีโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
export default class EditMovie extends Component {
state = {
movie: {},
isLoaded: false,
error: null,
};
componentDidMount() {
this.setState({
movie: {
title: "The Godfather",
mpaa_rating: "R",
}
});
}
render() {
let { movie } = this.state;
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<hr />
<form method="post">
<div className="mb-3">
<label for="title" className="form-label">
Title
</label>
<input
type="text"
className="form-control"
id="title"
name="title"
value={movie.title}
/>
</div>
<div className="mb-3">
<label for="release_date" className="form-label">
Release date
</label>
<input
type="text"
className="form-control"
id="release_date"
name="release_date"
value={movie.release_date}
/>
</div>
<div className="mb-3">
<label for="runtime" className="form-label">
Runtime
</label>
<input
type="text"
className="form-control"
id="runtime"
name="runtime"
value={movie.runtime}
/>
</div>
<div className="mb-3">
<label for="mpaa_rating" className="form-label">
MPAA Rating
</label>
<select className="form-select" value={movie.mpaa_rating}>
<option className="form-select">Choose...</option>
<option className="form-select" value="G">
G
</option>
<option className="form-select" value="PG">
PG
</option>
<option className="form-select" value="PG14">
PG13
</option>
<option className="form-select" value="R">
R
</option>
<option className="form-select" value="NC17">
NC17
</option>
</select>
</div>
<div className="mb-3">
<label for="rating" className="form-label">
Rating
</label>
<input
type="text"
className="form-control"
id="rating"
name="rating"
value={movie.rating}
/>
</div>
<div className="mb-3">
<label for="description" className="form-label">
Description
</label>
<textarea
className="form-control"
id="description"
name="description"
rows="3"
>
{movie.description}
</textarea>
</div>
<hr />
<button className="btn btn-primary">Save</button>
</form>
</Fragment>
);
}
}
ภายในโฟลเดอร์ components สร้างไฟล์ stylesheet ชื่อ EditMovie.css มีโค้ดดังนี้
label {
font-weight: bold;
}
ทดสอบการทำงาน npm start
ที่เว็บเบราเซอร์ ไปที่เมนู Add movie http://localhost:3000/admin/add จะแสดง ฟอร์ม ในการจัดการ Movie
Components แบบฟอร์ม ที่ควบคุมและผูกไว้กับ state
ไฟล์ EditMovie.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
export default class EditMovie extends Component {
state = {
movie: {},
isLoaded: false,
error: null,
};
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
isLoaded: false,
error: null,
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (evt) => {
console.log("Form was submitted");
evt.preventDefault();
}
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
}
}))
}
componentDidMount() {
}
render() {
let { movie } = this.state;
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<div className="mb-3">
<label htmlFor="title" className="form-label">
Title
</label>
<input
type="text"
className="form-control"
id="title"
name="title"
value={movie.title}
onChange={this.handleChange}
/>
</div>
<div className="mb-3">
<label htmlFor="release_date" className="form-label">
Release date
</label>
<input
type="text"
className="form-control"
id="release_date"
name="release_date"
value={movie.release_date}
onChange={this.handleChange}
/>
</div>
<div className="mb-3">
<label htmlFor="runtime" className="form-label">
Runtime
</label>
<input
type="text"
className="form-control"
id="runtime"
name="runtime"
value={movie.runtime}
onChange={this.handleChange}
/>
</div>
<div className="mb-3">
<label htmlFor="mpaa_rating" className="form-label">
MPAA Rating
</label>
<select name="mpaa_rating" className="form-select" value={movie.mpaa_rating} onChange={this.handleChange}>
<option className="form-select">Choose...</option>
<option className="form-select" value="G">
G
</option>
<option className="form-select" value="PG">
PG
</option>
<option className="form-select" value="PG13">
PG13
</option>
<option className="form-select" value="R">
R
</option>
<option className="form-select" value="NC17">
NC17
</option>
</select>
</div>
<div className="mb-3">
<label htmlFor="rating" className="form-label">
Rating
</label>
<input
type="text"
className="form-control"
id="rating"
name="rating"
value={movie.rating}
onChange={this.handleChange}
/>
</div>
<div className="mb-3">
<label htmlFor="description" className="form-label">
Description
</label>
<textarea
className="form-control"
id="description"
name="description"
rows="3"
onChange={this.handleChange}
value={movie.value}
/>
</div>
<hr />
<button className="btn btn-primary">Save</button>
</form>
<div className="mt-3">
<pre>{JSON.stringify(this.state, null, 3)}</pre>
</div>
</Fragment>
);
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Add movie http://localhost:3000/admin/add
และทดสอบการใช้งาน Form คีย์ Input ต่างๆ ที่ JSON ด้านล่างจะแสดงข้อมูลตามที่เราคีย์
Components แบบฟอร์มอินพุตที่ใช้ซ้ำได้
สร้างโฟเดอร์ form-components ภายในโฟลเดอร์ components แล้วสร้างไฟล์คอมโพเนนท์ ชื่อ Input.js เขียนโค้ดดังนี้
const Input = (props) => {
return (
<div className="mb-3">
<label htmlFor={props.name} className="form-label">
{props.title}
</label>
<input
type={props.type}
className="form-control"
id={props.name}
name={props.name}
value={props.value}
onChange={props.handleChange}
placeholder={props.placeholder}
/>
</div>
);
};
export default Input;
ภายในโฟลเดอร์ form-components สร้างไฟล์คอมโพเนนท์ ชื่อ TextArea.js เขียนโค้ดดังนี้
const TextArea = (props) => {
return (
<div className="mb-3">
<label htmlFor="description" className="form-label">
{props.title}
</label>
<textarea
className="form-control"
id={props.name}
name={props.name}
value={props.value}
onChange={props.handleChange}
rows={props.rows}
/>
</div>
);
};
export default TextArea;
ไฟล์ EditMovie.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
import Input from './form-components/Input';
import TextArea from './form-components/TextArea';
export default class EditMovie extends Component {
state = {
movie: {},
isLoaded: false,
error: null,
};
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
isLoaded: false,
error: null,
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (evt) => {
console.log("Form was submitted");
evt.preventDefault();
}
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
}
}))
}
componentDidMount() {
}
render() {
let { movie } = this.state;
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<Input
title={"Title"}
type={'text'}
name={'title'}
value={movie.title}
handleChange={this.handleChange}
/>
<Input
title={"Release Date"}
type={'date'}
name={'release_date'}
value={movie.release_date}
handleChange={this.handleChange}
/>
<Input
title={"Runtime"}
type={'text'}
name={'runtime'}
value={movie.runtime}
handleChange={this.handleChange}
/>
<div className="mb-3">
<label htmlFor="mpaa_rating" className="form-label">
MPAA Rating
</label>
<select name="mpaa_rating" className="form-select" value={movie.mpaa_rating} onChange={this.handleChange}>
<option className="form-select">Choose...</option>
<option className="form-select" value="G">
G
</option>
<option className="form-select" value="PG">
PG
</option>
<option className="form-select" value="PG13">
PG13
</option>
<option className="form-select" value="R">
R
</option>
<option className="form-select" value="NC17">
NC17
</option>
</select>
</div>
<Input
title={"Rating"}
type={'text'}
name={'rating'}
value={movie.rating}
handleChange={this.handleChange}
/>
{/* <div className="mb-3">
<label htmlFor="description" className="form-label">
Description
</label>
<textarea
className="form-control"
id="description"
name="description"
rows="3"
onChange={this.handleChange}
value={movie.description}
/>
</div> */}
<TextArea
title={"Description"}
name={"description"}
value={movie.description}
rows={"3"}
handleChange={this.handleChange}
/>
<hr />
<button className="btn btn-primary">Save</button>
</form>
<div className="mt-3">
<pre>{JSON.stringify(this.state, null, 3)}</pre>
</div>
</Fragment>
);
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Add movie http://localhost:3000/admin/add และทดสอบการใช้งาน Form คีย์ Input ต่างๆ ที่ JSON ด้านล่างจะแสดงข้อมูลตามที่เราคีย์
Components ที่เลือกนำมาใช้ใหม่ได้
ภายในโฟลเดอร์ form-components สร้างไฟล์คอมโพเนนท์ ชื่อ Select.js เขียนโค้ดดังนี้
const Select = (props) => {
return (
<div className="mb-3">
<label htmlFor={props.name} className="form-label">
{" "}
{props.title}{" "}
</label>
<select
className="form-select"
name={props.name}
value={props.value}
onChange={props.handleChange}
>
<option value="">{props.placeholder}</option>
{props.options.map((option) => {
return (
<option
className="form-select"
key={option.id}
value={option.id}
label={option.value}
>
{option.value}
</option>
)
})}
</select>
</div>
);
};
export default Select;
ไฟล์ EditMovie.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
import Input from './form-components/Input';
import TextArea from './form-components/TextArea';
import Select from './form-components/Select';
export default class EditMovie extends Component {
state = {
movie: {},
isLoaded: false,
error: null,
};
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
mpaaOptions: [
{id: "G", value: "G"},
{id: "PG", value: "PG"},
{id: "PG13", value: "PG13"},
{id: "R", value: "R"},
{id: "NC17", value: "NC17"},
],
isLoaded: false,
error: null,
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (evt) => {
console.log("Form was submitted");
evt.preventDefault();
}
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
}
}))
}
componentDidMount() {
}
render() {
let { movie } = this.state;
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<Input
title={"Title"}
type={'text'}
name={'title'}
value={movie.title}
handleChange={this.handleChange}
/>
<Input
title={"Release Date"}
type={'date'}
name={'release_date'}
value={movie.release_date}
handleChange={this.handleChange}
/>
<Input
title={"Runtime"}
type={'text'}
name={'runtime'}
value={movie.runtime}
handleChange={this.handleChange}
/>
{/* <div className="mb-3">
<label htmlFor="mpaa_rating" className="form-label">
MPAA Rating
</label>
<select name="mpaa_rating" className="form-select" value={movie.mpaa_rating} onChange={this.handleChange}>
<option className="form-select">Choose...</option>
<option className="form-select" value="G">
G
</option>
<option className="form-select" value="PG">
PG
</option>
<option className="form-select" value="PG13">
PG13
</option>
<option className="form-select" value="R">
R
</option>
<option className="form-select" value="NC17">
NC17
</option>
</select>
</div> */}
<Select
title={'MPAA Rating'}
name={'mpaa_rating'}
options={this.state.mpaaOptions}
value={movie.mpaa_rating}
handleChange={this.handleChange}
placeholder={'Choose...'}
/>
<Input
title={"Rating"}
type={'text'}
name={'rating'}
value={movie.rating}
handleChange={this.handleChange}
/>
<TextArea
title={"Description"}
name={"description"}
value={movie.description}
rows={"3"}
handleChange={this.handleChange}
/>
<hr />
<button className="btn btn-primary">Save</button>
</form>
<div className="mt-3">
<pre>{JSON.stringify(this.state, null, 3)}</pre>
</div>
</Fragment>
);
}
}
ที่เว็บเบราเซอร์ ไปที่เมนู Add movie http://localhost:3000/admin/add
การคีย์แบบฟอร์มล่วงหน้าด้วย movie ที่มีอยู่
ไฟล์ EditMovie.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
import Input from "./form-components/Input";
import TextArea from "./form-components/TextArea";
import Select from "./form-components/Select";
export default class EditMovie extends Component {
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
mpaaOptions: [
{ id: "G", value: "G" },
{ id: "PG", value: "PG" },
{ id: "PG13", value: "PG13" },
{ id: "R", value: "R" },
{ id: "NC17", value: "NC17" },
],
isLoaded: false,
error: null,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (evt) => {
console.log("Form was submitted");
evt.preventDefault();
};
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
},
}));
};
componentDidMount() {
const id = this.props.match.params.id;
if (id > 0) {
fetch("http://localhost:4000/v1/movie/" + id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.Message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
const releaseDate = new Date(json.movie.release_date);
this.setState(
{
movie: {
id: id,
title: json.movie.title,
release_date: releaseDate.toISOString().split("T")[0],
runtime: json.movie.runtime,
mpaa_rating: json.movie.mpaa_rating,
rating: json.movie.rating,
description: json.movie.description,
},
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
} else {
this.setState({ isLoaded: true });
}
}
render() {
let { movie, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<Input
title={"Title"}
type={"text"}
name={"title"}
value={movie.title}
handleChange={this.handleChange}
/>
<Input
title={"Release Date"}
type={"date"}
name={"release_date"}
value={movie.release_date}
handleChange={this.handleChange}
/>
<Input
title={"Runtime"}
type={"text"}
name={"runtime"}
value={movie.runtime}
handleChange={this.handleChange}
/>
<Select
title={"MPAA Rating"}
name={"mpaa_rating"}
options={this.state.mpaaOptions}
value={movie.mpaa_rating}
handleChange={this.handleChange}
placeholder={"Choose..."}
/>
<Input
title={"Rating"}
type={"text"}
name={"rating"}
value={movie.rating}
handleChange={this.handleChange}
/>
<TextArea
title={"Description"}
name={"description"}
value={movie.description}
rows={"3"}
handleChange={this.handleChange}
/>
<hr />
<button className="btn btn-primary">Save</button>
</form>
<div className="mt-3">
<pre>{JSON.stringify(this.state, null, 3)}</pre>
</div>
</Fragment>
);
}
}
}
ที่ไฟล์ App.js เขียนโค้ด ดังนี้
import React, { Component, Fragment } from 'react';
import {BrowserRouter as Router, Switch, Route, Link, useParams, useRouteMatch} from 'react-router-dom';
import Movies from './components/Movies';
import Admin from './components/Admin';
import Home from './components/Home';
import OneMovie from './components/OneMovie';
import Genres from './components/Genres';
import OneGenre from './components/OneGenre';
import EditMovie from './components/EditMovie';
export default function App() {
return (
<Router>
<div className="container">
<div className="row">
<h1 className="mt-3">
Go Watch a Movie!
</h1>
<hr className="mb-3"></hr>
</div>
<div className="row">
<div className="col-md-2">
<nav>
<ul className="list-group">
<li className="list-group-item">
<Link to="/">Home</Link>
</li>
<li className="list-group-item">
<Link to="/movies">Movies</Link>
</li>
<li className="list-group-item">
<Link to="/genres">Genres</Link>
</li>
<li className="list-group-item">
<Link to="/admin/movie/0">Add movie</Link>
</li>
<li className="list-group-item">
<Link to="/admin">Manage Catalogue</Link>
</li>
</ul>
</nav>
</div>
<div className="col-md-10">
<Switch>
<Route path="/movies/:id" component={OneMovie} />
<Route path="/movies">
<Movies />
</Route>
<Route path="/genre/:id" component={OneGenre} />
<Route exact path="/genres">
<Genres />
</Route>
<Route path="/admin/movie/:id" component={EditMovie} />
<Route path="/admin">
<Admin />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</div>
</div>
</Router>
);
}
เปิดการทำงานของ Go Back-End REST API และ ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/1
ส่งข้อมูลไปที่ Back-End REST
ที่ Front-End ไฟล์ EditMovie.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
import Input from "./form-components/Input";
import TextArea from "./form-components/TextArea";
import Select from "./form-components/Select";
export default class EditMovie extends Component {
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
mpaaOptions: [
{ id: "G", value: "G" },
{ id: "PG", value: "PG" },
{ id: "PG13", value: "PG13" },
{ id: "R", value: "R" },
{ id: "NC17", value: "NC17" },
],
isLoaded: false,
error: null,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (evt) => {
evt.preventDefault();
const data = new FormData(evt.target);
const payload = Object.fromEntries(data.entries());
console.log(payload);
const requestOptions = {
method: 'POST',
body : JSON.stringify(payload)
}
fetch('http://localhost:4000/v1/admin/editmovie', requestOptions)
.then(response => response.json())
.then(data => {
console.log(data);
})
};
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
},
}));
};
componentDidMount() {
const id = this.props.match.params.id;
if (id > 0) {
fetch("http://localhost:4000/v1/movie/" + id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.Message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
const releaseDate = new Date(json.movie.release_date);
this.setState(
{
movie: {
id: id,
title: json.movie.title,
release_date: releaseDate.toISOString().split("T")[0],
runtime: json.movie.runtime,
mpaa_rating: json.movie.mpaa_rating,
rating: json.movie.rating,
description: json.movie.description,
},
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
} else {
this.setState({ isLoaded: true });
}
}
render() {
let { movie, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<Input
title={"Title"}
type={"text"}
name={"title"}
value={movie.title}
handleChange={this.handleChange}
/>
<Input
title={"Release Date"}
type={"date"}
name={"release_date"}
value={movie.release_date}
handleChange={this.handleChange}
/>
<Input
title={"Runtime"}
type={"text"}
name={"runtime"}
value={movie.runtime}
handleChange={this.handleChange}
/>
<Select
title={"MPAA Rating"}
name={"mpaa_rating"}
options={this.state.mpaaOptions}
value={movie.mpaa_rating}
handleChange={this.handleChange}
placeholder={"Choose..."}
/>
<Input
title={"Rating"}
type={"text"}
name={"rating"}
value={movie.rating}
handleChange={this.handleChange}
/>
<TextArea
title={"Description"}
name={"description"}
value={movie.description}
rows={"3"}
handleChange={this.handleChange}
/>
<hr />
<button className="btn btn-primary">Save</button>
</form>
<div className="mt-3">
<pre>{JSON.stringify(this.state, null, 3)}</pre>
</div>
</Fragment>
);
}
}
}
ที่ Back-End ไฟล์ routes.go เพิ่มโค้ดดังนี้
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
func (app *application) routes() http.Handler {
router := httprouter.New()
router.HandlerFunc(http.MethodGet, "/status", app.statusHandler)
router.HandlerFunc(http.MethodGet, "/v1/movie/:id", app.getOneMovie)
router.HandlerFunc(http.MethodGet, "/v1/movies", app.getAllMovies)
router.HandlerFunc(http.MethodGet, "/v1/movies/:genre_id", app.getAllMoviesByGenre)
router.HandlerFunc(http.MethodGet, "/v1/genres", app.getAllGenres)
router.HandlerFunc(http.MethodPost, "/v1/admin/editmovie", app.editmovie)
return app.enableCORS(router)
}
ไฟล์ movie-handlers เพิ่มโค้ดดังนี้
package main
import (
"errors"
"net/http"
"strconv"
"github.com/julienschmidt/httprouter"
)
func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
id, err := strconv.Atoi(params.ByName("id"))
if err != nil {
app.logger.Print(errors.New("invalid id parameter"))
app.errorJSON(w, err)
return
}
movie, err := app.models.DB.Get(id)
err = app.writeJSON(w, http.StatusOK, movie, "movie")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMovies(w http.ResponseWriter, r *http.Request) {
movies, err := app.models.DB.All()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllGenres(w http.ResponseWriter, r *http.Request) {
genres, err := app.models.DB.GenresAll()
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, genres, "genres")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) getAllMoviesByGenre(w http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
genreID, err := strconv.Atoi(params.ByName("genre_id"))
if err != nil {
app.errorJSON(w, err)
return
}
movies, err := app.models.DB.All(genreID)
if err != nil {
app.errorJSON(w, err)
return
}
err = app.writeJSON(w, http.StatusOK, movies, "movies")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) deleteMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) insertMovie(w http.ResponseWriter, r *http.Request) {
}
func (app *application) editmovie(w http.ResponseWriter, r *http.Request) {
type jsonResp struct {
OK bool `json:"ok"`
}
ok := jsonResp {
OK: true,
}
err := app.writeJSON(w, http.StatusOK, ok, "response")
if err != nil {
app.errorJSON(w, err)
return
}
}
func (app *application) searchMovies(w http.ResponseWriter, r *http.Request) {
}
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/0 คีย์ข้อมูล -> Save
ที่ Console จะแสดงข้อมูลรูปแบบ JSON (แต่ยังไม่ไม่ได้เพิ่มข้อมูลลงในฐานข้อมูล)
การตรวจสอบแบบฟอร์มฝั่งไคลเอ็นต์
Form Validation คือ การตรวจสอบข้อมูลของฟอร์ม เป็นขั้นตอนที่ช่วยในการตรวจสอบให้ข้อมูลที่รับจากฟอร์ม HTML มีความถูกต้อง ตรงตามที่ต้องการให้มากที่สุด ซึ่งนิยมนำไปใช้มากที่สุดคือการตรวจสอบ (Validation) form ของผู้ใช้ในหน้าเว็บ ซึ่งเป็นเว็บไซต์แบบที่มีการกรอกข้อมูล
ที่ Front-End ไฟล์ EditMovie.js แก้ไขโค้ดดังนี้
import React, { Component, Fragment } from "react";
import "./EditMovie.css";
import Input from "./form-components/Input";
import TextArea from "./form-components/TextArea";
import Select from "./form-components/Select";
export default class EditMovie extends Component {
constructor(props) {
super(props);
this.state = {
movie: {
id: 0,
title: "",
release_date: "",
runtime: "",
mpaa_rating: "",
rating: "",
description: "",
},
mpaaOptions: [
{ id: "G", value: "G" },
{ id: "PG", value: "PG" },
{ id: "PG13", value: "PG13" },
{ id: "R", value: "R" },
{ id: "NC17", value: "NC17" },
],
isLoaded: false,
error: null,
errors: [],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit = (evt) => {
evt.preventDefault();
// client side validation
let errors = [];
if (this.state.movie.title === "") {
errors.push("title");
}
this.setState({errors: errors});
if (errors.length > 0) {
return false;
}
const data = new FormData(evt.target);
const payload = Object.fromEntries(data.entries());
console.log(payload);
const requestOptions = {
method: 'POST',
body : JSON.stringify(payload)
}
fetch('http://localhost:4000/v1/admin/editmovie', requestOptions)
.then(response => response.json())
.then(data => {
console.log(data);
})
};
handleChange = (evt) => {
let value = evt.target.value;
let name = evt.target.name;
this.setState((prevState) => ({
movie: {
...prevState.movie,
[name]: value,
},
}));
};
hasError(key) {
return this.state.errors.indexOf(key) !== -1;
}
componentDidMount() {
const id = this.props.match.params.id;
if (id > 0) {
fetch("http://localhost:4000/v1/movie/" + id)
.then((response) => {
if (response.status !== "200") {
let err = Error;
err.Message = "Invalid response code: " + response.status;
this.setState({ error: err });
}
return response.json();
})
.then((json) => {
const releaseDate = new Date(json.movie.release_date);
this.setState(
{
movie: {
id: id,
title: json.movie.title,
release_date: releaseDate.toISOString().split("T")[0],
runtime: json.movie.runtime,
mpaa_rating: json.movie.mpaa_rating,
rating: json.movie.rating,
description: json.movie.description,
},
isLoaded: true,
},
(error) => {
this.setState({
isLoaded: true,
error,
});
}
);
});
} else {
this.setState({ isLoaded: true });
}
}
render() {
let { movie, isLoaded, error } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <p>Loading...</p>;
} else {
return (
<Fragment>
<h2>Add/Edit Movie</h2>
<hr />
<form onSubmit={this.handleSubmit}>
<input
type="hidden"
name="id"
id="id"
value={movie.id}
onChange={this.handleChange}
/>
<Input
title={"Title"}
className={this.hasError("title") ? "is-invalid" : ""}
type={"text"}
name={"title"}
value={movie.title}
handleChange={this.handleChange}
errorDiv={this.hasError("title") ? "text-danger" : "d-none"}
errorMsg={"Please enter a title"}
/>
<Input
title={"Release Date"}
type={"date"}
name={"release_date"}
value={movie.release_date}
handleChange={this.handleChange}
/>
<Input
title={"Runtime"}
type={"text"}
name={"runtime"}
value={movie.runtime}
handleChange={this.handleChange}
/>
<Select
title={"MPAA Rating"}
name={"mpaa_rating"}
options={this.state.mpaaOptions}
value={movie.mpaa_rating}
handleChange={this.handleChange}
placeholder={"Choose..."}
/>
<Input
title={"Rating"}
type={"text"}
name={"rating"}
value={movie.rating}
handleChange={this.handleChange}
/>
<TextArea
title={"Description"}
name={"description"}
value={movie.description}
rows={"3"}
handleChange={this.handleChange}
/>
<hr />
<button className="btn btn-primary">Save</button>
</form>
<div className="mt-3">
<pre>{JSON.stringify(this.state, null, 3)}</pre>
</div>
</Fragment>
);
}
}
}
ไฟล์ Input.js แก้ไขโค้ดดังนี้
const Input = (props) => {
return (
<div className="mb-3">
<label htmlFor={props.name} className="form-label">
{props.title}
</label>
<input
type={props.type}
className={`form-control ${props.className}`}
id={props.name}
name={props.name}
value={props.value}
onChange={props.handleChange}
placeholder={props.placeholder}
/>
<div className={props.errorDiv}>{props.errorMsg}</div>
</div>
);
};
export default Input;
ที่เว็บเบราเซอร์ ไปที่ http://localhost:3000/admin/movie/0 ไม่ได้คีย์ข้อมูล -> Save
credit : https://www.udemy.com/course/working-with-react-and-go-golang/