こんにちは、KOUKIです。
GolangとReactでMovie Appの開発をしています。
前回に引き続き、Movie Formの処理を実装します。
尚、Udemyの「Working with React and Go (Golang)」を参考にしているので、よかったら受講してみてください。
<目次>
前回
Movieデータの保存や更新を行うMovie Formの実装を行いました。
事前準備
フォルダ/ファイル
1 2 3 |
mkdir go-movies/src/components/ui-components touch go-movies/src/components/ui-components/Alert.tsx touch go-movies/src/models/ui-components.ts |
Alert機能
Formからデータの登録や更新を行なった後の結果をAlertで表示できるようにします。
Alertコンポーネント
BootStrapのAlert機能を使って、Alertコンポーネントを実装します。
1 2 3 4 5 6 7 8 9 10 |
// components/ui-components/Alert.tsx const Alert = (props: any) => { return ( <div className={`alert ${props.alertType}`} role="alert"> {props.alertMessage} </div> ) } export default Alert |
Alertの設定
EditMovieコンポーネントからAlertの設定を行いましょう。
まずは、Alertの型を定義します。
1 2 3 4 5 |
// src/models/ui-components.ts export interface AlertType { type: string message: string } |
型の定義は、Golang感覚で実装してますね。GolangとTypeScriptは似てます。
次に、useStateでAlertを管理します。
1 2 3 4 5 6 7 8 9 |
// components/EditMovie.tsx import { AlertType } from '../models/ui-components' ... const EditMovie = (props: any) => { ... const [alert, setAlert] = useState<AlertType>({type: "d-none", message: ""}) ... } |
Requestの修正
Alertを設定できる様にRequestを修正します。
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 |
// components/EditMovie.tsx ... const EditMovie = (props: any) => { .... const submit = async (e: any) => { ... await axios.post('admin/editmovie', JSON.stringify(movie)) .then((response) => { setAlert({ type: 'alert-success', message: 'Changes saved!' }) }) .catch((err) => { setError(err.message) setAlert({ type: 'alert-danger', message: err.response.data.error.message }) } } ... if (error) { // return ( // <div>Error: {error}</div> // ) } else if (!isLoaded) { return ( <p>Loading...</p> ) } ... } |
Alertコンポーネントの設置
先ほど実装したAlertコンポーネントを設置しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// components/EditMovie.tsx ... import Alert from './ui-components/Alert' ... const EditMovie = (props: any) => { ... return ( <Fragment> <h2>Add/Edit Movie</h2> <Alert alertType={alert.type} alertMessage={alert.message} /> ... </Fragment> ) } export default EditMovie |
動作確認
docker-compose up
でコンテナを立ち上げ、ブラウザから「http://localhost:3000/admin/movie/0」のFormへアクセスします。
Formの項目を適当に埋め、「Save」ボタンを押下してください。

OKですね。エラー表示については、後ほどお見せします。
編集機能
Movieデータの編集機能を実装しましょう。
UpdateMovie
Golang側で、Update処理を実装します。
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 |
// models/movies-db.go ... func (m *DBModel) UpdateMovie(movie Movie) error { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stmt := `update into movies set title = $1, description = $2, year = $3, release_date = $4, runtime = $5, rating = $6, mpaa_rating = $7 ,created_at = $8, updated_at = $9 where id = $10` _, err := m.DB.ExecContext(ctx, stmt, movie.Title, movie.Description, movie.Year, movie.ReleaseDate, movie.Runtime, movie.Rating, movie.MPAARating, movie.CreatedAt, movie.UpdatedAt, movie.ID, ) if err != nil { return err } return nil } |
実は、クエリにバグ(into)があるのですが、エラー表示時の画面確認に使いますので後ほど修正します。
ハンドラーの修正
ハンドラーにUpdate用の処理を追加します。
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 |
// cmd/api/movie-handlers.go ... func (app *application) editMovie(w http.ResponseWriter, r *http.Request) { ... var movie models.Movie if payload.ID != "0" { id, _ := strconv.Atoi(payload.ID) // idからMovieデータを取得 m, _ := app.models.DB.Get(id) // ポインタを渡してm == movieにする movie = *m movie.UpdatedAt = time.Now() } ... if movie.ID == 0 { ... } else { err = app.models.DB.UpdateMovie(movie) if err != nil { app.errorJSON(w, err) return } } ok := jsonResp{ OK: true, } err = app.writeJSON(w, http.StatusOK, ok, "response") if err != nil { app.errorJSON(w, err) return } } |
不具合1: Movieデータが編集できない
今頃気がついたのですが、編集画面に入力されるデータを編集することができません。
Reactでは、input要素などのvalue属性は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 106 107 108 109 110 111 112 113 114 115 116 117 118 |
// components/EditMovie.tsx ... const EditMovie = (props: any) => { ... useEffect(() => { ( async () => { ... if (id > 0) { // Edit await axios.get(`movie/${id}`) .then((response) => { let movie = response.data.movie const releaseDate = new Date(movie.release_date) movie.release_date = releaseDate.toISOString().split("T")[0] setMovie(movie) setTitle(movie.title) setDescription(movie.description) setReleaseDate(movie.release_date) setRuntime(movie.runtime) setRating(movie.rating) setMpaaRating(movie.mpaa_rating) setIsLoaded(true) }) .catch((err) => { setError(err.message) }) } else { // New setIsLoaded(true) } } )() }, [id]) ... return ( <Fragment> <h2>Add/Edit Movie</h2> <Alert alertType={alert.type} alertMessage={alert.message} /> <hr /> <form method="post" onSubmit={submit}> <Input title={"Title"} className={hasError("title") ? "is-invalid" : ""} type={'text'} name={'title'} // value={movie?.title} value={title} handleChange={setTitle} errorDiv={hasError("title") ? "text-danger" : "d-none"} errorMsg={"Please enter a title"} /> <Input title={"Release Date"} className={hasError("release_date") ? "is-invalid" : ""} type={'date'} name={'release_date'} // value={movie?.release_date} value={releaseDate} handleChange={setReleaseDate} errorDiv={hasError("release_date") ? "text-danger" : "d-none"} errorMsg={"Please enter a release_date"} /> <Input title={"Runtime"} className={hasError("runtime") ? "is-invalid" : ""} type={'text'} name={'runtime'} // value={movie?.runtime} value={runtime} handleChange={setRuntime} errorDiv={hasError("runtime") ? "text-danger" : "d-none"} errorMsg={"Please enter a runtime"} /> <Select title={'MPAA Rating'} name={'mpaa_rating'} options={mpaaOptions} // value={movie?.mpaa_rating} value={mpaaRating} handleChange={setMpaaRating} placeholder={'Choose...'} /> <Input title={"Rating"} className={hasError("rating") ? "is-invalid" : ""} type={'text'} name={'rating'} // value={movie?.rating} value={rating} handleChange={setRating} errorDiv={hasError("rating") ? "text-danger" : "d-none"} errorMsg={"Please enter a rating"} /> <TextArea title={"Description"} name={'description'} // value={movie?.description} value={description} handleChange={setDescription} rows={3} /> <hr /> <button className="btn btn-primary" type="submit">Save</button> </form> </Fragment> ) } export default EditMovie |
「setXXXX」にてStateを設定し、value属性にその値を指定しました。これで、編集できるようになります。
不具合2: 400エラーの修正
修正画面で、「Save」ボタンを押下すると400エラーになります。
エラーメッセージには、以下の内容が表示されていました。
1 |
message: "json: cannot unmarshal number into Go struct field MoviePayload.runtime of type string" |
runtimeの型に誤りがあるようなので、修正しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// cmd/api/movie-handlers.go ... type MoviePayload struct { ID string `json:"id"` Title string `json:"title"` Description string `json:"Description"` Year string `json:"year"` ReleaseDate string `json:"release_date"` RunTime int `json:"runtime"` // string -> int Rating int `json:"rating"` // string -> int MPAARating string `json:"mpaa_rating"` } func (app *application) editMovie(w http.ResponseWriter, r *http.Request) { ... movie.Runtime = payload.RunTime movie.Rating = payload.Rating ... } |
runtimeに加えてratingもintタイプなので、合わせて修正しました。
また、Formに手入力した値を取得するとstring型になるので、それも修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// components/EditMovie.ts const EditMovie = (props: any) => { ... const submit = async (e: any) => { e.preventDefault() const movie: Movie = { id: id, title: title, description: description, year: "2021", release_date: releaseDate, runtime: Number(runtime), rating: Number(rating), // 数値に強制変換 mpaa_rating: mpaaRating, // 数値に強制変換 genres: [] } ... } |
修正3:SQL(クエリ)の修正
「pq: syntax error at or near “into”」エラーが出たので、修正します。

1 2 3 4 5 6 7 8 |
// models/movies-db.go func (m *DBModel) InsertMovie(movie Movie) error { ... stmt := `insert movies (title, description, year, release_date, runtime, // intoを削除 rating, mpaa_rating, created_at, updated_at) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)` } |
動作検証
ようやく動作検証ができます。
次回
次回は、FormからMovieデータを削除する処理を実装します。
記事まとめ
参考書籍
ソースコード
movie-handlers.go
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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
// cmd/api/movie-handlers.go package main import ( "backend/models" "encoding/json" "errors" "log" "net/http" "strconv" "time" "github.com/julienschmidt/httprouter" ) func (app *application) getOneMovie(w http.ResponseWriter, r *http.Request) { params := httprouter.ParamsFromContext(r.Context()) // urlからidを抜きだす 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) getAllMovie(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) getAllMoviesByGenres(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 } } type MoviePayload struct { ID string `json:"id"` Title string `json:"title"` Description string `json:"Description"` Year string `json:"year"` ReleaseDate string `json:"release_date"` RunTime int `json:"runtime"` Rating int `json:"rating"` MPAARating string `json:"mpaa_rating"` } type jsonResp struct { OK bool `json:"ok"` Message string `json:"message"` } func (app *application) editMovie(w http.ResponseWriter, r *http.Request) { var payload MoviePayload err := json.NewDecoder(r.Body).Decode(&payload) if err != nil { log.Println(err) app.errorJSON(w, err) return } var movie models.Movie if payload.ID != "0" { id, _ := strconv.Atoi(payload.ID) // idからMovieデータを取得 m, _ := app.models.DB.Get(id) // ポインタを渡してm == movieにする movie = *m movie.UpdatedAt = time.Now() } movie.ID, _ = strconv.Atoi(payload.ID) movie.Title = payload.Title movie.Description = payload.Description movie.ReleaseDate, _ = time.Parse("2006-01-02", payload.ReleaseDate) movie.Year = movie.ReleaseDate.Year() movie.Runtime = payload.RunTime movie.Rating = payload.Rating movie.MPAARating = payload.MPAARating movie.CreatedAt = time.Now() movie.UpdatedAt = time.Now() if movie.ID == 0 { err = app.models.DB.InsertMovie(movie) if err != nil { app.errorJSON(w, err) return } } else { err = app.models.DB.UpdateMovie(movie) if err != nil { app.errorJSON(w, err) return } } ok := jsonResp{ OK: true, } err = app.writeJSON(w, http.StatusOK, ok, "response") if err != nil { app.errorJSON(w, err) return } } |
movies-db.go
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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
// models/movies-db.go package models import ( "context" "database/sql" "fmt" "time" ) type DBModel struct { DB *sql.DB } // Get retuns one movie and error, if any func (m *DBModel) Get(id int) (*Movie, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() query := `select id, title, description, year, release_date, rating, runtime, mpaa_rating, created_at, updated_at from movies where id = $1` row := m.DB.QueryRowContext(ctx, query, id) var movie Movie err := row.Scan( &movie.ID, &movie.Title, &movie.Description, &movie.Year, &movie.ReleaseDate, &movie.Rating, &movie.Runtime, &movie.MPAARating, &movie.CreatedAt, &movie.UpdatedAt, ) if err != nil { return nil, err } // get genres, if any query = `select mg.id, mg.movie_id, mg.genre_id, g.genre_name from movies_genres mg left join genres g on (g.id = mg.genre_id) where mg.movie_id = $1` rows, _ := m.DB.QueryContext(ctx, query, id) defer rows.Close() genres := make(map[int]string) for rows.Next() { var mg MovieGenre err := rows.Scan( &mg.ID, &mg.MovieID, &mg.GenreID, &mg.Genre.GenreName, ) if err != nil { return nil, err } genres[mg.ID] = mg.Genre.GenreName } movie.MovieGenre = genres return &movie, nil } // All retuns all movies and error, if any func (m *DBModel) All(genre ...int) ([]*Movie, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() where := "" if len(genre) > 0 { where = fmt.Sprintf( "where id in (select movie_id from movies_genres where genre_id = %d)", genre[0]) } query := fmt.Sprintf( `select id, title, description, year, release_date, rating, runtime, mpaa_rating, created_at, updated_at from movies %s order by title`, where) rows, err := m.DB.QueryContext(ctx, query) if err != nil { return nil, err } defer rows.Close() var movies []*Movie for rows.Next() { var movie Movie err := rows.Scan( &movie.ID, &movie.Title, &movie.Description, &movie.Year, &movie.ReleaseDate, &movie.Rating, &movie.Runtime, &movie.MPAARating, &movie.CreatedAt, &movie.UpdatedAt, ) if err != nil { return nil, err } genreQuery := `select mg.id, mg.movie_id, mg.genre_id, g.genre_name from movies_genres mg left join genres g on (g.id = mg.genre_id) where mg.movie_id = $1` genreRows, _ := m.DB.QueryContext(ctx, genreQuery, movie.ID) genres := make(map[int]string) for genreRows.Next() { var mg MovieGenre err := genreRows.Scan( &mg.ID, &mg.MovieID, &mg.GenreID, &mg.Genre.GenreName, ) if err != nil { return nil, err } genres[mg.ID] = mg.Genre.GenreName } genreRows.Close() movie.MovieGenre = genres movies = append(movies, &movie) } return movies, nil } func (m *DBModel) GenresAll() ([]*Genre, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() query := `select id, genre_name, created_at, updated_at from genres order by genre_name` rows, err := m.DB.QueryContext(ctx, query) if err != nil { return nil, err } defer rows.Close() var genres []*Genre for rows.Next() { var g Genre err := rows.Scan( &g.ID, &g.GenreName, &g.CreatedAt, &g.UpdatedAt, ) if err != nil { return nil, err } genres = append(genres, &g) } return genres, nil } func (m *DBModel) InsertMovie(movie Movie) error { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stmt := `insert movies (title, description, year, release_date, runtime, rating, mpaa_rating, created_at, updated_at) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)` _, err := m.DB.ExecContext(ctx, stmt, movie.Title, movie.Description, movie.Year, movie.ReleaseDate, movie.Runtime, movie.Rating, movie.MPAARating, movie.CreatedAt, movie.UpdatedAt, ) if err != nil { return err } return nil } func (m *DBModel) UpdateMovie(movie Movie) error { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stmt := `update movies set title = $1, description = $2, year = $3, release_date = $4, runtime = $5, rating = $6, mpaa_rating = $7, created_at = $8, updated_at = $9 where id = $10` _, err := m.DB.ExecContext(ctx, stmt, movie.Title, movie.Description, movie.Year, movie.ReleaseDate, movie.Runtime, movie.Rating, movie.MPAARating, movie.CreatedAt, movie.UpdatedAt, movie.ID, ) if err != nil { return err } return nil } |
ui-components.ts
1 2 3 4 5 6 |
// src/models/ui-components.ts export interface AlertType { type: string message: string } |
EditMovie.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 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 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
// components/EditMovie.tsx import { Fragment, useEffect, useState } from "react" import { AlertType } from '../models/ui-components' import { Movie } from '../models/movie' import Input from './form-components/Input' import TextArea from './form-components/TextArea' import Select from './form-components/Select' import Alert from './ui-components/Alert' import './EditMovie.css' import axios from 'axios' const EditMovie = (props: any) => { const [movie, setMovie] = useState<Movie>() const [isLoaded, setIsLoaded] = useState(false) const [error, setError] = useState("") const [errors, setErrors] = useState<string[]>([]) const [alert, setAlert] = useState<AlertType>({type: "d-none", message: ""}) // 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"}, ]) // URLからIDを取得 setID(props.match.params.id) if (id > 0) { // Edit await axios.get(`movie/${id}`) .then((response) => { let movie = response.data.movie const releaseDate = new Date(movie.release_date) movie.release_date = releaseDate.toISOString().split("T")[0] setMovie(movie) setTitle(movie.title) setDescription(movie.description) setReleaseDate(movie.release_date) setRuntime(movie.runtime) setRating(movie.rating) setMpaaRating(movie.mpaa_rating) setIsLoaded(true) }) .catch((err) => { setError(err.message) }) } else { // New setIsLoaded(true) } } )() }, [id]) const submit = async (e: any) => { e.preventDefault() const movie: Movie = { id: id, title: title, description: description, year: "2021", release_date: releaseDate, runtime: Number(runtime), rating: Number(rating), mpaa_rating: mpaaRating, genres: [] } // validation let localErrors: string[] = [] if (movie?.title === "") { localErrors.push("title") } if (movie?.release_date === "") { localErrors.push("release_date") } if (movie?.runtime === 0) { localErrors.push("runtime") } if (movie?.rating === 0) { localErrors.push("rating") } // Errorセット setErrors(localErrors) if (errors.length > 0) { return false } await axios.post('admin/editmovie', JSON.stringify(movie)) .then((response) => { setAlert({ type: 'alert-success', message: 'Changes saved!' }) }) .catch((err) => { setError(err.message) setAlert({ type: 'alert-danger', message: err.response.data.error.message }) }) } const hasError = (key: string) => { return errors.indexOf(key) !== -1 } if (error) { // return ( // <div>Error: {error}</div> // ) } else if (!isLoaded) { return ( <p>Loading...</p> ) } return ( <Fragment> <h2>Add/Edit Movie</h2> <Alert alertType={alert.type} alertMessage={alert.message} /> <hr /> <form method="post" onSubmit={submit}> <Input title={"Title"} className={hasError("title") ? "is-invalid" : ""} type={'text'} name={'title'} value={title} handleChange={setTitle} errorDiv={hasError("title") ? "text-danger" : "d-none"} errorMsg={"Please enter a title"} /> <Input title={"Release Date"} className={hasError("release_date") ? "is-invalid" : ""} type={'date'} name={'release_date'} value={releaseDate} handleChange={setReleaseDate} errorDiv={hasError("release_date") ? "text-danger" : "d-none"} errorMsg={"Please enter a release_date"} /> <Input title={"Runtime"} className={hasError("runtime") ? "is-invalid" : ""} type={'text'} name={'runtime'} value={runtime} handleChange={setRuntime} errorDiv={hasError("runtime") ? "text-danger" : "d-none"} errorMsg={"Please enter a runtime"} /> <Select title={'MPAA Rating'} name={'mpaa_rating'} options={mpaaOptions} value={mpaaRating} handleChange={setMpaaRating} placeholder={'Choose...'} /> <Input title={"Rating"} className={hasError("rating") ? "is-invalid" : ""} type={'text'} name={'rating'} value={rating} handleChange={setRating} errorDiv={hasError("rating") ? "text-danger" : "d-none"} errorMsg={"Please enter a rating"} /> <TextArea title={"Description"} name={'description'} value={description} handleChange={setDescription} rows={3} /> <hr /> <button className="btn btn-primary" type="submit">Save</button> </form> </Fragment> ) } export default EditMovie |
Alert.tsx
1 2 3 4 5 6 7 8 9 10 11 |
// components/ui-components/Alert.tsx const Alert = (props: any) => { return ( <div className={`alert ${props.alertType}`} role="alert"> {props.alertMessage} </div> ) } export default Alert |
コメントを残す
コメントを投稿するにはログインしてください。