こんにちは、KOUKIです。
GolangとReactでMovie Appの開発をしています。
今回は、Formの作成方法について紹介します。
尚、Udemyの「Working with React and Go (Golang)」を参考にしているので、よかったら受講してみてください。
前回
Movieデータに付随してついてくるGeres(ジャンル)データを処理しました。
事前準備
フォルダ/ファイル
1 2 3 4 5 6 |
touch go-movies/src/components/EditMovie.tsx touch go-movies/src/components/EditMovie.css mkdir go-movies/src/components/form-components touch go-movies/src/components/form-components/Input.tsx <meta charset="utf-8">touch go-movies/src/components/form-components/TextArea.tsx touch go-movies/src/components/form-components/Select.tsx |
Movie Formの作成
Movie情報を追加するための、Formを作成しましょう。
EditMovieコンポーネント
コンポーネントを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 |
// components/EditMovie.tsx import { Fragment } from "react" const EditMovie = (props: any) => { return ( <Fragment> EditMovie </Fragment> ) } export default EditMovie |
ルート
ルートを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// src/App.tsx ... import EditMovie from './components/EditMovie' export default function App() { return ( ... <div className="row"> <div className="col-md-2"> <nav> ... <li className="list-group-item"> <Link to="/admin/add">Add Movie</Link> // 追加 </li> ... </ul> </nav> </div> <div className="col-md-10"> <Switch> ... <Route path="/admin/add" component={EditMovie}/> // 追加 ... </Switch> </div> </div> </div> </Router> ) } |
検証
下記のコマンドで、Dockerコンテナを立ち上げてください。
1 |
docker-compose up |
ブラウザから「http://localhost:3000/」にアクセスして開発画面を表示し、「Add Movie」を押してみましょう。

Formの完成
EditMovieコンポーネントにForm要素を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
// components/EditMovie.tsx import { Fragment, useEffect, useState } from "react" import { Movie } from '../models/movie' import './EditMovie.css' const EditMovie = (props: any) => { const [movie, setMovie] = useState<Movie>() const [isLoaded, setIsLoaded] = useState(false) const [error, setError] = useState("") return ( <Fragment> <h2>Add/Edit Movie</h2> <hr /> <form method="post"> <input type="hidden" name="id" id="id" value={movie?.id} /> <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} /> </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} /> </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} /> </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}> <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">PG14</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} /> </div> <div className="mb-3"> <label htmlFor="description" className="form-label"> Description </label> <textarea className="form-control" id="description" name="description" rows={3} value={movie?.description} /> </div> <hr /> <button className="btn btn-primary" type="submit">Save</button> </form> </Fragment> ) } export default EditMovie |
CSSも追加します。
1 2 3 4 |
/* components/EditMovie.css */ label { font-weight: bold; } |

Formに値を反映する
先ほど実装したFormで、Movieデータを新規に追加します。加えて、Movieデータを“Edit(編集)”するときにも使いたいと思います。
各要素には、「value」要素をセットしているので、movie変数にデータが入っていれば、値が反映されるように実装しました。
サンプルとして、Movieデータを手動で追加してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// components/EditMovie.tsx ... const EditMovie = (props: any) => { const [movie, setMovie] = useState<Movie>() const [isLoaded, setIsLoaded] = useState(false) const [error, setError] = useState("") const sampleData: Movie = { // サンプルデータの追加 id: 0, title: "Harry Potter", description: "The Masic World", year: 2004, release_date: "2024", runtime: 175, rating: 10, mpaa_rating: "R", genres: ["Fantasy"] } useEffect(() => { ( async () => { setMovie(sampleData) } )() }, []) ... } |
useEffectを使用すると、ページを開いたタイミングでMovieデータを保存することができます。
ブラウザから「http://localhost:3000/admin/add」へアクセスし、SampleDataが表示されるか確認しましょう。

Submit処理
FormからAPIへSubmitする処理を実装しましょう。
Submit関数
最初にSubmit関数を定義し、formにセットしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// components/EditMovie.tsx ... const EditMovie = (props: any) => { ... const submit = async (e: SyntheticEvent) => { e.preventDefault() console.log("register") } return ( <Fragment> <h2>Add/Edit Movie</h2> <hr /> <form method="post" onSubmit={submit}> // 追加 .... </form> </Fragment> ) } export default EditMovie |
Changeイベント
formに入力された値をStateにセットします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
// components/EditMovie.tsx ... const EditMovie = (props: any) => { ... // form data const [id, setID] = useState(0) const [title, setTitle] = useState("") const [description, setDescription] = useState("") const [releaseDate, setReleaseDate] = useState("") const [runtime, setRuntime] = useState(0) const [rating, setRating] = useState(0) const [mpaaRating, setMpaaRating] = useState("") useEffect(() => { ( async () => { // setMovie(sampleData) } )() }, []) const submit = async (e: SyntheticEvent) => { e.preventDefault() const sampleData: Movie = { id: id, title: title, description: description, release_date: releaseDate, year: 0, runtime: runtime, rating: rating, mpaa_rating: mpaaRating, genres: [] } console.log(sampleData) console.log("register") } return ( <Fragment> <h2>Add/Edit Movie</h2> <hr /> <form method="post" onSubmit={submit}> <input ... onChange={e => setID(+e.target.value)} // 追加 /> <div className="mb-3"> ... <input ... onChange={e => setTitle(e.target.value)}<meta charset="utf-8"> // 追加 /> </div> <div className="mb-3"> ... <input ... onChange={e => setReleaseDate(e.target.value)}<meta charset="utf-8"> // 追加 /> </div> <div className="mb-3"> ... <input ... onChange={e => setRuntime(+e.target.value)}<meta charset="utf-8"> // 追加 /> </div> <div className="mb-3"> ... <select ... onChange={e => setMpaaRating(e.target.value)}<meta charset="utf-8"> // 追加 > ... </select> </div> <div className="mb-3"> ... <input ... onChange={e => setRating(+e.target.value)}<meta charset="utf-8"> // 追加 /> </div> <div className="mb-3"> ... <textarea ... onChange={e => setDescription(e.target.value)}<meta charset="utf-8"> // 追加 /> </div> <hr /> <button className="btn btn-primary" type="submit">Save</button> </form> </Fragment> ) } export default EditMovie |
これで、Formの値を変更後、リアルタイムにStateへ値が反映されます。

Input要素のコンポーネント化
Input要素をコンポーネント化して、コードを最適化しましょう。
Inputコンポーネント
EditMovieコンポーネントに実装したInput要素で共通する部分を、Inputコンポーネントにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// components/form-components/input.tsx const Input = (props: any) => { 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={e => props.handleChange(e.target.value)} /> </div> ) } export default Input |
コンポーネントの入れ替え
先ほど実装したInputコンポーネントを使ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// components/EditMovie.tsx ... import Input from './form-components/Input' const EditMovie = (props: any) => { ... return ( <Fragment> <h2>Add/Edit Movie</h2> <hr /> <form method="post" onSubmit={submit}> <Input title={"Title"} type={'text'} name={'title'} value={movie?.title} handleChange={setTitle} /> <Input title={"Release Date"} type={'date'} name={'release_date'} value={movie?.release_date} handleChange={setReleaseDate} /> <Input title={"Runtime"} type={'text'} name={'runtime'} value={movie?.runtime} handleChange={setRuntime} /> <div className="mb-3"> .... </div> <Input title={"Rating"} type={'text'} name={'rating'} value={movie?.rating} handleChange={setRating} /> <div className="mb-3"> .... </div> <hr /> <button className="btn btn-primary" type="submit">Save</button> </form> </Fragment> ) } export default EditMovie |
検証
Input要素にデータを入力して、入れ替え前と同じように使えるか確認してみましょう。

TextArea要素のコンポーネント化
Inputと同様に、TextAreaもコンポーネント化しましょう。
TextAreaコンポーネント
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// components/form-components/TextArea.tsx const TextArea = (props: any) => { return ( <div className="mb-3"> <label htmlFor={props.name} className="form-label"> {props.title} </label> <textarea className="form-control" id={props.name} name={props.name} value={props.value} onChange={e => props.handleChange(e.target.value)} rows={props.rows} /> </div> ) } export default TextArea |
コンポーネントの入れ替え
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// components/EditMovie.tsx ... import TextArea from './form-components/TextArea' const EditMovie = (props: any) => { .... return ( <Fragment> <h2>Add/Edit Movie</h2> <hr /> <form method="post" onSubmit={submit}> ... <TextArea title={"Description"} name={'description'} value={movie?.description} handleChange={setDescription} rows={3} /> <hr /> <button className="btn btn-primary" type="submit">Save</button> </form> </Fragment> ) } export default EditMovie |
検証

Select要素のコンポーネント化
Selectもコンポーネント化します。
Selectコンポーネント
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// components/form-components/Select.tsx const Select = (props: any) => { 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={e => props.handleChange(e.target.value)} > <option value="">{props.placeholder}</option> {props.options.map((option: any) => { return ( <option className="form-select" key={option.id} value={option.id} label={option.value} > {option.value} </option> ) })} </select> </div> ) } export default Select |
コンポーネントの入れ替え
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// components/EditMovie.tsx ... import Select from './form-components/Select' const EditMovie = (props: any) => { ... // form data ... const [mpaaOptions, setMpaaOptions] = useState([{}]) useEffect(() => { ( async () => { setMpaaOptions([ {id: "G", value: "G"}, {id: "PG", value: "PG"}, {id: "PG13", value: "PG13"}, {id: "R", value: "R"}, {id: "NC17", value: "NC17"}, ]) // setMovie(sampleData) } )() }, []) ... return ( <Fragment> <h2>Add/Edit Movie</h2> <hr /> <form method="post" onSubmit={submit}> ... <Select title={'MPAA Rating'} name={'mpaa_rating'} options={mpaaOptions} values={movie?.mpaa_rating} handleChange={setMpaaRating} placeholder={'Choose...'} /> ... </form> </Fragment> ) } export default EditMovie |
検証

次回
記事まとめ
参考書籍
ソースコード
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
// src/App.tsx import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom' import Home from './components/Home' import Movies from './components/Movies' import Admin from './components/Admin' import Genres from './components/Genres' import OneMovie from './components/OneMovie' 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" /> </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 Catalog</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> ) } |
EditMovie.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
// components/EditMovie.tsx import { SyntheticEvent, Fragment, useEffect, useState } from "react" import { Movie } from '../models/movie' import Input from './form-components/Input' import TextArea from './form-components/TextArea' import Select from './form-components/Select' import './EditMovie.css' const EditMovie = (props: any) => { const [movie, setMovie] = useState<Movie>() const [isLoaded, setIsLoaded] = useState(false) const [error, setError] = useState("") // form data const [id, setID] = useState(0) const [title, setTitle] = useState("") const [description, setDescription] = useState("") const [releaseDate, setReleaseDate] = useState("") const [runtime, setRuntime] = useState(0) const [rating, setRating] = useState(0) const [mpaaRating, setMpaaRating] = useState("") const [mpaaOptions, setMpaaOptions] = useState([{}]) useEffect(() => { ( async () => { setMpaaOptions([ {id: "G", value: "G"}, {id: "PG", value: "PG"}, {id: "PG13", value: "PG13"}, {id: "R", value: "R"}, {id: "NC17", value: "NC17"}, ]) // setMovie(sampleData) } )() }, []) const submit = async (e: SyntheticEvent) => { e.preventDefault() const sampleData: Movie = { id: id, title: title, description: description, release_date: releaseDate, year: 0, runtime: runtime, rating: rating, mpaa_rating: mpaaRating, genres: [] } console.log(sampleData) } return ( <Fragment> <h2>Add/Edit Movie</h2> <hr /> <form method="post" onSubmit={submit}> <Input title={"Title"} type={'text'} name={'title'} value={movie?.title} handleChange={setTitle} /> <Input title={"Release Date"} type={'date'} name={'release_date'} value={movie?.release_date} handleChange={setReleaseDate} /> <Input title={"Runtime"} type={'text'} name={'runtime'} value={movie?.runtime} handleChange={setRuntime} /> <Select title={'MPAA Rating'} name={'mpaa_rating'} options={mpaaOptions} values={movie?.mpaa_rating} handleChange={setMpaaRating} placeholder={'Choose...'} /> <Input title={"Rating"} type={'text'} name={'rating'} value={movie?.rating} handleChange={setRating} /> <TextArea title={"Description"} name={'description'} value={movie?.description} handleChange={setDescription} rows={3} /> <hr /> <button className="btn btn-primary" type="submit">Save</button> </form> </Fragment> ) } export default EditMovie |
EditMovie.css
1 2 3 4 |
/* components/EditMovie.css */ label { font-weight: bold; } |
Input.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// components/form-components/Input.tsx const Input = (props: any) => { 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={e => props.handleChange(e.target.value)} /> </div> ) } export default Input |
TextArea.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// components/form-components/TextArea.tsx const TextArea = (props: any) => { return ( <div className="mb-3"> <label htmlFor={props.name} className="form-label"> {props.title} </label> <textarea className="form-control" id={props.name} name={props.name} value={props.value} onChange={e => props.handleChange(e.target.value)} rows={props.rows} /> </div> ) } export default TextArea |
Select.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// components/form-components/Select.tsx const Select = (props: any) => { 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={e => props.handleChange(e.target.value)} > <option value="">{props.placeholder}</option> {props.options.map((option: any) => { return ( <option className="form-select" key={option.id} value={option.id} label={option.value} > {option.value} </option> ) })} </select> </div> ) } export default Select |
コメントを残す
コメントを投稿するにはログインしてください。