Melanjutkan dari modul latihan sebelumnya, pada modul ini kita akan menampilkan list anime berdasarkan genre.
Hasil akhir dari latihan yang diharapkan adalah seperti gambar berikut.

Ada dua pendekatan, Anda bisa membuat fungsi baru untuk mengambil data anime berdasarkan genre.
Atau kita lakukan sedikit modifikasi pada fungsi getAnimes (fungsi yang menampilkan seluruh list anime).
Pada modul ini akan dibahas dengan pendekatan kedua, yaitu modifikasi fungsi getAnimes.
Solusi
Bagian Backend
Buka file cmd/api/animeModel.go, lakukan modifikasi pada fungsi All().
Modifikasi agar fungsi All menerima parameter genre_id.
func (m *AnimeModel) All(genre ...int) ([]*Anime, error) {
Lalu modifikasi query statment
where := ""
if len(genre) > 0 {
where = fmt.Sprintf("WHERE id IN (SELECT anime_id FROM animes_genres WHERE genre_id= %d)", genre[0])
}
query := fmt.Sprintf(`SELECT id, title, description, year
FROM animes %s ORDER BY title`, where)
Berikut isi lengkap file cmd/api/animeModel.go
func (m *AnimeModel) All(genre ...int) ([]*Anime, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
where := ""
if len(genre) > 0 {
where = fmt.Sprintf("WHERE id IN (SELECT anime_id FROM animes_genres WHERE genre_id= %d)", genre[0])
}
query := fmt.Sprintf(`SELECT id, title, description, year
FROM animes %s ORDER BY title`, where)
rows, err := m.DB.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var animes []*Anime
for rows.Next() {
var anime Anime
err = rows.Scan(
&anime.Id,
&anime.Title,
&anime.Desc,
&anime.Year,
)
if err != nil {
return nil, err
}
query2 := `SELECT ag.id, ag.anime_id, ag.genre_id, g.genre_name
FROM animes_genres ag
LEFT JOIN genres g ON (g.id = ag.id)
WHERE ag.anime_id = $1
`
rows2, _ := m.DB.QueryContext(ctx, query2, anime.Id)
genre := make(map[int]string)
for rows2.Next() {
var ag animeGenre
err = rows2.Scan(
&ag.Id,
&ag.AnimeId,
&ag.GenreId,
&ag.Genre.GenreName,
)
if err != nil {
return nil, err
}
genre[ag.Id] = ag.Genre.GenreName
}
rows2.Close()
anime.AnimeGenre = genre
animes = append(animes, &anime)
}
return animes, nil
}
Tambahkan routing pada cmd/api/routes.go
router.HandlerFunc(http.MethodGet, "/v1/animes/:genre_id", app.getAnimeByGenre)
Tambahkan fungsi untuk handler movie berdasarkan genre pada cmd/api/animesHandlers.go
func (app *application) getAnimeByGenre(rw http.ResponseWriter, r *http.Request) {
params := httprouter.ParamsFromContext(r.Context())
genreid, err := strconv.Atoi(params.ByName("genre_id"))
if err != nil {
app.errorJSON(rw, err)
return
}
animes, err := app.models.Animes.All(genreid)
app.logger.Println(genreid)
err = app.writeJSON(rw, http.StatusOK, animes, "animes")
if err != nil {
app.errorJSON(rw, err)
}
}
Bagian Frontend
Buat component baru src/components/Genre.js isi dengan code berikut
import React, {Component, Fragment} from 'react';
import { Link } from 'react-router-dom';
export default class Genre extends Component{
state = {
animes : [],
isLoaded : false,
genreName : "",
}
componentDidMount(){
fetch("http://localhost:4000/v1/animes/" + this.props.match.params.id)
.then((response) => response.json())
.then((json) =>{
this.setState({
animes: json.animes,
isLoaded: true,
genreName : this.props.location.genrename,
})
console.log(json)
})
}
render(){
let {animes, isLoaded, genreName} = this.state;
if (!animes){
animes = []
}
if(!isLoaded){
return <p>Data is loading...</p>
}else{
return(
<Fragment>
<h2>Anime List - {genreName}</h2>
<ul>
{animes.map((e) =>(
<li key={e.id}>
<Link to={`/animes/${e.id}`}>{e.title}</Link>
</li>
))}
</ul>
</Fragment>
);
}
}
}
Pada src/App.js, tambahkan route baru pada element <Switch> untuk handle movie by genre
<Route path="/genre/:id" component={Genre} />
Dan tambahkan import component yang digunakan.
import Genre from './components/Genre';
Berikut isi lengkap src/App.js
import React from 'react';
import {BrowserRouter as Router, Switch, Route, Link} from 'react-router-dom';
import Home from './components/Home';
import Animes from './components/Animes';
import Admin from './components/Admin';
import Genres from './components/Genres';
import Anime from './components/Anime';
import Genre from './components/Genre';
export default function App() {
return (
<Router>
<div className="container">
<div className="row">
<h1 className="mt-3">Anime Collection</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="/animes">Anime</Link></li>
<li className="list-group-item"><Link to="/genres">Genre</Link></li>
<li className="list-group-item"><Link to="/admin">Admin</Link></li>
</ul>
</nav>
</div>
<div className="col-md-10">
<Switch>
<Route path="/animes/:id" component={Anime} />
<Route path="/animes"><Animes /></Route>
<Route path="/genre/:id" component={Genre} />
<Route exact path="/genres"><Genres /></Route>
<Route path="/admin"><Admin /></Route>
<Route path="/"><Home /></Route>
</Switch>
</div>
</div>
</div>
</Router>
);
}