- 投稿日:2020-10-23T21:19:27+09:00
5.TMDB リスト いいね 通知
はじめに
1.アプリケーション紹介
2.認証機能
3.TMDB api fetch
4.ジャンルフィルターとページ移動
5.リスト いいね 通知今回はログイン後の機能でリストといいねと公開前の映画を通知希望のリストに追加するとヘッダーで通知するようにします。
コードに関しては、githubにて後悔しておりますので、こちらからどうぞ!リスト
こんな感じでユーザーリストを作理、リストに追加できるようにします。
まずはじめにfirestoreないから、、、
firebase内のデータベース設計
ユーザー1人に、folderコレクションを作ります。
そこにはユーザーとは別のコレクションのfolderに映画をセットしていくようになります。下記がユーザーの中のfolderコレクションなります。こちらのidからfolderコレクションと紐付けしていきます。
そしてこちらがユーザーとは別のfolderコレクションの中身で実際にはこちらに映画をセットしていきます。
リスト作成
export const makeFolder = (uid: string, folderName: string) => { return async (dispatch:any) => { const ref = folderRef.doc() const folderId = ref.id folderRef.doc(folderId).set({ created_at: FirebaseTimestamp.now(), name: folderName, uid: uid, }) usersRef.doc(uid).collection('folder').doc(folderId).set({ name: folderName, id: folderId, created_at: FirebaseTimestamp.now(), }) } }まずfolderコレクションに作成日時とfolderの名前と作成者をセット、
そしてユーザーのfolderコレクションにも紐付けを行うためにfolderIdをセットします。リスト削除
export const deleteFolder = (uid: string, folderId: string) => { return async (dispatch:any) => { folderRef.doc(folderId).delete() usersRef.doc(uid).collection('folder').doc(folderId).delete() } }削除ボタンを押すと削除できるようにこちらも書いておきます。
リストへの追加
export const addFolderMovie = (folderId: string, movie:movie) => { return async (dispatch: any) => { folderRef.doc(folderId).collection('movie').get() .then(snapshot => { const match = [] snapshot.docs.forEach(doc => { const data = doc.data() if(data.id === movie.id){ match.push(data) } }) if(match.length > 0){ return false }else{ const ref = usersRef.doc(folderId).collection('movie').doc() const movieId = ref.id const data = { movieId: movieId, id: movie.id, title: movie.title, poster_path: movie.poster_path, backdrop_path: movie.backdrop_path, release_date: movie.release_date, genres: movie.genres, overview: movie.overview, vote_average: movie.vote_average, timestamp: FirebaseTimestamp.now() } folderRef.doc(folderId).collection('movie').doc(movieId).set(data) } }) } }これがfolderへの追加のコードですが、同じ映画がセットされることのないように一度、追加するfolder内の映画を取得して入ってきた映画とかぶっていないか確認しています。
そして被っていなければ映画をセットするようにしています。リストから映画を削除
export const deleteFolderMovie = (folderId:string, movie: movie) => { return async (dispatch:any) => { folderRef.doc(folderId).collection('movie').get() .then(snapshot => { const match:any = [] snapshot.docs.forEach(doc => { const data = doc.data() if(data.id === movie.id){ match.push(data) } }) if(match.length === 0){ return false }else{ folderRef.doc(folderId).collection('movie').doc(match[0].movieId).delete() } }) } }こちらでも同様に削除するものがなければfalseを返しています。
そしてあればfirestore内から削除するようにしています。これがリストの作成、削除、映画の追加、削除の処理になります。
いいね
こちらはユーザーログイン時にデフォルトで入っているお気に入り機能になります。
そのため、先ほどのリストへ映画追加と削除のコードとほとんど同じですので詳しい説明は省きます。export const deleteFavoriteMovie = (id: number) => { return async (dispatch:any, getState:any) => { const uid = getState().user.uid usersRef.doc(uid).collection('favorite').get() .then(snapshot => { const match:any = [] snapshot.docs.filter(doc => { const data = doc.data() if(data.id === id){ match.push(data) } }) if(match.length === 0){ return false }else{ console.log(match) usersRef.doc(uid).collection('favorite').doc(match[0].movieId).delete() } }) } } export const addFavoriteMovie = (movie: movie) => { return async (dispatch: any, getState:any) => { const uid = getState().user.uid usersRef.doc(uid).collection('favorite').get() .then(snapshot => { const match = snapshot.docs.filter(doc => { const data = doc.data() return data.id === movie.id }) if(match.length > 0){ return false }else{ const ref = usersRef.doc(uid).collection('favorite').doc() const movieId = ref.id const data = { movieId: movieId, id: movie.id, title: movie.title, poster_path: movie.poster_path, backdrop_path: movie.backdrop_path, release_date: movie.release_date, genres: movie.genres, overview: movie.overview, vote_average: movie.vote_average, timestamp: FirebaseTimestamp.now() } usersRef.doc(uid).collection('favorite').doc(movieId).set(data) } }) } }公開作品通知
ユーザーコレクションのnotificationコレクションを用意、こちらも同様に初ログインからデフォルトで入っているリストになります。
そして未公開の場合にだけこちらのリストに追加できるようにします。
これに関しては、映画のfetchした情報の中に公開日も入っているので、その日にちがまだきていなければnotificationコレクションへの追加ボタンを用意します。今回のコードはいいねの追加と全く同じなので追加と削除のコードは省きます。
firestoreからnotificatioコレクションをfetchしてきて公開日から一週間を切った場合、ヘッダーから通知されるようになります。
const [message, setMessage] = useState("") useEffect(() => { const release = movie.release_date.split('-') const year = release[0] const month = release[1] const date = release[2] const releaseDate = `${year}/${month}/${date} 00:00:00` let today:any = new Date() const data:any = Date.parse(releaseDate) const item = data - today if(item > 0){ if(item < 86400000){ setMessage("明日公開!!") }else if(item < 172800000){ setMessage('残り2日!') }else if(item < 259200000){ setMessage('残り3日!') }else if(item < 345600000){ setMessage('残り4日!') }else if(item < 432000000){ setMessage('残り5日!') }else if(item < 518400000){ setMessage('残り6日!') }else if(item < 604800000){ setMessage('残り7日!') } }else{ if(item > -604800000){ setMessage('公開中') }else{ db.collection('user').doc(displayUid).collection('notification').doc(movie.movieId).delete() } } },[])ここでは、fetchした映画の配列を回し、映画オブジェクトを渡されている状態です。
そして渡ってきた映画が公開日から何日過ぎているか残り7日から通知するようになっています。
公開まで7日以上の場合は、通知されないようになっています。
公開してから一週間がすぎると、自動でnotificationコレクションから削除するようします。終わりに
今回でこのアプリについての記事を終わろうと思います。
主な機能の実装方法のみ記事にしております。ので他の詳しいコードなどが知りたい方はこちらのgithubからどうぞ!今回、UI構築は、material-uiをふんだんに使いました。
そのおかげでデザインの知識がない私でも、充実したものになったので、本当に便利だと感じました。
そしてそのデザインもTMDB公式のアプリを大いに似せていただきました。これから勉強を重ねていきたいと思っております。
- 投稿日:2020-10-23T21:19:27+09:00
6.TMDB リスト いいね 通知
はじめに
1.アプリケーション紹介
2.認証機能
3.TMDB api fetch
4.auto-suggestion検索フォーム
5.ジャンルフィルターとページ移動
6.リスト いいね 通知今回はログイン後の機能でリストといいねと公開前の映画を通知希望のリストに追加するとヘッダーで通知するようにします。
コードに関しては、githubにて後悔しておりますので、こちらからどうぞ!リスト
こんな感じでユーザーリストを作理、リストに追加できるようにします。
まずはじめにfirestoreないから、、、
firebase内のデータベース設計
ユーザー1人に、folderコレクションを作ります。
そこにはユーザーとは別のコレクションのfolderに映画をセットしていくようになります。下記がユーザーの中のfolderコレクションなります。こちらのidからfolderコレクションと紐付けしていきます。
そしてこちらがユーザーとは別のfolderコレクションの中身で実際にはこちらに映画をセットしていきます。
リスト作成
export const makeFolder = (uid: string, folderName: string) => { return async (dispatch:any) => { const ref = folderRef.doc() const folderId = ref.id folderRef.doc(folderId).set({ created_at: FirebaseTimestamp.now(), name: folderName, uid: uid, }) usersRef.doc(uid).collection('folder').doc(folderId).set({ name: folderName, id: folderId, created_at: FirebaseTimestamp.now(), }) } }まずfolderコレクションに作成日時とfolderの名前と作成者をセット、
そしてユーザーのfolderコレクションにも紐付けを行うためにfolderIdをセットします。リスト削除
export const deleteFolder = (uid: string, folderId: string) => { return async (dispatch:any) => { folderRef.doc(folderId).delete() usersRef.doc(uid).collection('folder').doc(folderId).delete() } }削除ボタンを押すと削除できるようにこちらも書いておきます。
リストへの追加
export const addFolderMovie = (folderId: string, movie:movie) => { return async (dispatch: any) => { folderRef.doc(folderId).collection('movie').get() .then(snapshot => { const match = [] snapshot.docs.forEach(doc => { const data = doc.data() if(data.id === movie.id){ match.push(data) } }) if(match.length > 0){ return false }else{ const ref = usersRef.doc(folderId).collection('movie').doc() const movieId = ref.id const data = { movieId: movieId, id: movie.id, title: movie.title, poster_path: movie.poster_path, backdrop_path: movie.backdrop_path, release_date: movie.release_date, genres: movie.genres, overview: movie.overview, vote_average: movie.vote_average, timestamp: FirebaseTimestamp.now() } folderRef.doc(folderId).collection('movie').doc(movieId).set(data) } }) } }これがfolderへの追加のコードですが、同じ映画がセットされることのないように一度、追加するfolder内の映画を取得して入ってきた映画とかぶっていないか確認しています。
そして被っていなければ映画をセットするようにしています。リストから映画を削除
export const deleteFolderMovie = (folderId:string, movie: movie) => { return async (dispatch:any) => { folderRef.doc(folderId).collection('movie').get() .then(snapshot => { const match:any = [] snapshot.docs.forEach(doc => { const data = doc.data() if(data.id === movie.id){ match.push(data) } }) if(match.length === 0){ return false }else{ folderRef.doc(folderId).collection('movie').doc(match[0].movieId).delete() } }) } }こちらでも同様に削除するものがなければfalseを返しています。
そしてあればfirestore内から削除するようにしています。これがリストの作成、削除、映画の追加、削除の処理になります。
いいね
こちらはユーザーログイン時にデフォルトで入っているお気に入り機能になります。
そのため、先ほどのリストへ映画追加と削除のコードとほとんど同じですので詳しい説明は省きます。export const deleteFavoriteMovie = (id: number) => { return async (dispatch:any, getState:any) => { const uid = getState().user.uid usersRef.doc(uid).collection('favorite').get() .then(snapshot => { const match:any = [] snapshot.docs.filter(doc => { const data = doc.data() if(data.id === id){ match.push(data) } }) if(match.length === 0){ return false }else{ console.log(match) usersRef.doc(uid).collection('favorite').doc(match[0].movieId).delete() } }) } } export const addFavoriteMovie = (movie: movie) => { return async (dispatch: any, getState:any) => { const uid = getState().user.uid usersRef.doc(uid).collection('favorite').get() .then(snapshot => { const match = snapshot.docs.filter(doc => { const data = doc.data() return data.id === movie.id }) if(match.length > 0){ return false }else{ const ref = usersRef.doc(uid).collection('favorite').doc() const movieId = ref.id const data = { movieId: movieId, id: movie.id, title: movie.title, poster_path: movie.poster_path, backdrop_path: movie.backdrop_path, release_date: movie.release_date, genres: movie.genres, overview: movie.overview, vote_average: movie.vote_average, timestamp: FirebaseTimestamp.now() } usersRef.doc(uid).collection('favorite').doc(movieId).set(data) } }) } }公開作品通知
ユーザーコレクションのnotificationコレクションを用意、こちらも同様に初ログインからデフォルトで入っているリストになります。
そして未公開の場合にだけこちらのリストに追加できるようにします。
これに関しては、映画のfetchした情報の中に公開日も入っているので、その日にちがまだきていなければnotificationコレクションへの追加ボタンを用意します。今回のコードはいいねの追加と全く同じなので追加と削除のコードは省きます。
firestoreからnotificatioコレクションをfetchしてきて公開日から一週間を切った場合、ヘッダーから通知されるようになります。
const [message, setMessage] = useState("") useEffect(() => { const release = movie.release_date.split('-') const year = release[0] const month = release[1] const date = release[2] const releaseDate = `${year}/${month}/${date} 00:00:00` let today:any = new Date() const data:any = Date.parse(releaseDate) const item = data - today if(item > 0){ if(item < 86400000){ setMessage("明日公開!!") }else if(item < 172800000){ setMessage('残り2日!') }else if(item < 259200000){ setMessage('残り3日!') }else if(item < 345600000){ setMessage('残り4日!') }else if(item < 432000000){ setMessage('残り5日!') }else if(item < 518400000){ setMessage('残り6日!') }else if(item < 604800000){ setMessage('残り7日!') } }else{ if(item > -604800000){ setMessage('公開中') }else{ db.collection('user').doc(displayUid).collection('notification').doc(movie.movieId).delete() } } },[])ここでは、fetchした映画の配列を回し、映画オブジェクトを渡されている状態です。
そして渡ってきた映画が公開日から何日過ぎているか残り7日から通知するようになっています。
公開まで7日以上の場合は、通知されないようになっています。
公開してから一週間がすぎると、自動でnotificationコレクションから削除するようします。終わりに
今回でこのアプリについての記事を終わろうと思います。
主な機能の実装方法のみ記事にしております。ので他の詳しいコードなどが知りたい方はこちらのgithubからどうぞ!今回、UI構築は、material-uiをふんだんに使いました。
そのおかげでデザインの知識がない私でも、充実したものになったので、本当に便利だと感じました。
そしてそのデザインもTMDB公式のアプリを大いに似せていただきました。これから勉強を重ねていきたいと思っております。
- 投稿日:2020-10-23T21:19:02+09:00
4.TMDB ジャンルフィルターとページ移動
はじめに
1.アプリケーション紹介
2.認証機能
3.TMDB api fetch
4.ジャンルフィルターとページ移動
5.リスト いいね 通知アプリの概要など他にもこのアプリケーションについての記事を載せておりますのでそちらの方がみたい方はそちらをどうぞ!
コードもgithubにてのせておりますので、こちらからどうぞ!
今回は、ジャンルを選択したときにフィルターにかけてapiをたたいて映画の取得を行います。そしてページ遷移ができるコードも載せていきたい思っております。
ジャンルフィルター
こんな感じのジャンルフィルターを作っていきます。
//selectGenreは、ジャンルボタンを選択した時にそのジャンルをこのstateにせっとする。この中にジャンルが入ってくる! const [selectGenre, setSelectGenre] = useState<genre[]>([]) //ジャンルボタンの選択後の挙動 const toggleGenre = (genre: genre) => { const filteredGenres = selectGenre.filter((g:genre) => g.id !== genre.id) if(filteredGenres.length === selectGenre.length){ setSelectGenre([ ...filteredGenres, genre, ]) }else{ setSelectGenre([ ...filteredGenres, ]) } } //selectGenreがセットされるたびにfetchするようにする useEffect(() => { const genreIDs = selectGenre.map((g: genre):number => { return g.id }) if(path === '/'){ dispatch(fetchMovieList(API_GET_MOVIE_POPULAR, genreIDs)) }else if(path === '/upcoming'){ dispatch(fetchMovieList(API_GET_MOVIE_UPCOMING, genreIDs)) }else if(path === '/now_playing'){ dispatch(fetchMovieList(API_GET_MOVIE_NOW_PLAYING, genreIDs)) }else if(path === '/top_rated'){ dispatch(fetchMovieList(API_GET_MOVIE_TOP_RATED, genreIDs)) } },[selectGenre, path]) export const API_GET_MOVIE_POPULAR = 'movie/popular'; export const API_GET_MOVIE_UPCOMING = 'movie/upcoming' export const API_GET_MOVIE_NOW_PLAYING = 'movie/now_playing' export const API_GET_MOVIE_TOP_RATED = 'movie/top_rated'1.selectGenreをフィルターにかけて、一個目にセットしたジャンルとかぶっていた場合は、それ以外をstateにセットする、またかぶっていなければそのままstateをセット
2.そしてselectGenreのstateがセットされるごとにデータをfetchする(ジャンルボタンをクリック)
今回は、公開中(/now_playing)、人気(/)、高評価(top_rated)、新作公開(upcoming)も同じコンポーネントで表示するのでパスによってfetchするときのURIが変わるようにしています。ページ遷移
//次にページをセット const prevPage = (page - 1) <= 0 ? 1 : (page - 1); //ページを一個戻る挙動 const nextPage = (page + 1) > total_pages ? total_pages : (parseInt(Page, 10) + parseInt('1', 10)); //そして次へボタンへのchangePageは、nextPageを戻るボタンへのchangePageは、prevPageを渡す const changePage = (page: number) => { if(page === 0){ alert('該当の作品はありませんでした。') return false }else{ if(typeof(Storage) !== 'undefined'){ localStorage.setItem('currentPage', JSON.stringify(page)) } const GenresID = selectGenre.map((g:genre) => g.id) if(path === '/'){ dispatch(fetchMovieList(API_GET_MOVIE_POPULAR, GenresID, page)) }else if(path === '/upcoming'){ dispatch(fetchMovieList(API_GET_MOVIE_UPCOMING, GenresID, page)) }else if(path === '/now_playing'){ dispatch(fetchMovieList(API_GET_MOVIE_NOW_PLAYING, GenresID, page)) }else if(path === '/top_rated'){ dispatch(fetchMovieList(API_GET_MOVIE_TOP_RATED, GenresID, page)) } } } return( <button type="button" title="Previous 20 movies" onClick={() => changePage(prevPage)} > Prev </button> <div> {page} <span> / </span> {total_pages} </div> <button type="button" title="Next 20 movies" onClick={() => changePage(nextPage)} > Next </button> )1.prevPageは、page数が1以外の場合は、pageステートを-1する
2.nextPageは、fetchしてきた情報の中にトータルのページ数も入っているので、そのトータルページ数とpageステートが同じではない場合は、pageを+1するようになっている。
3.このprevPageは、戻るボタンをクリックした時にchangePageの引数として渡す。次へボタンを押したらnextPageを下記のように引数として渡す。
4.前、次のページのボタンがクリックされるとpageステートがそれに応じて変化してそれを各pathの映画のfetchメソッドに渡すことでpageを戻ったり、次へ進んだりできるようになる。
- 投稿日:2020-10-23T21:19:02+09:00
5.TMDB ジャンルフィルターとページ移動
はじめに
1.アプリケーション紹介
2.認証機能
3.TMDB api fetch
4.auto-suggestion検索フォーム
5.ジャンルフィルターとページ移動
6.リスト いいね 通知アプリの概要など他にもこのアプリケーションについての記事を載せておりますのでそちらの方がみたい方はそちらをどうぞ!
コードもgithubにてのせておりますので、こちらからどうぞ!
今回は、ジャンルを選択したときにフィルターにかけてapiをたたいて映画の取得を行います。そしてページ遷移ができるコードも載せていきたい思っております。
ジャンルフィルター
こんな感じのジャンルフィルターを作っていきます。
//selectGenreは、ジャンルボタンを選択した時にそのジャンルをこのstateにせっとする。この中にジャンルが入ってくる! const [selectGenre, setSelectGenre] = useState<genre[]>([]) //ジャンルボタンの選択後の挙動 const toggleGenre = (genre: genre) => { const filteredGenres = selectGenre.filter((g:genre) => g.id !== genre.id) if(filteredGenres.length === selectGenre.length){ setSelectGenre([ ...filteredGenres, genre, ]) }else{ setSelectGenre([ ...filteredGenres, ]) } } //selectGenreがセットされるたびにfetchするようにする useEffect(() => { const genreIDs = selectGenre.map((g: genre):number => { return g.id }) if(path === '/'){ dispatch(fetchMovieList(API_GET_MOVIE_POPULAR, genreIDs)) }else if(path === '/upcoming'){ dispatch(fetchMovieList(API_GET_MOVIE_UPCOMING, genreIDs)) }else if(path === '/now_playing'){ dispatch(fetchMovieList(API_GET_MOVIE_NOW_PLAYING, genreIDs)) }else if(path === '/top_rated'){ dispatch(fetchMovieList(API_GET_MOVIE_TOP_RATED, genreIDs)) } },[selectGenre, path]) export const API_GET_MOVIE_POPULAR = 'movie/popular'; export const API_GET_MOVIE_UPCOMING = 'movie/upcoming' export const API_GET_MOVIE_NOW_PLAYING = 'movie/now_playing' export const API_GET_MOVIE_TOP_RATED = 'movie/top_rated'1.selectGenreをフィルターにかけて、一個目にセットしたジャンルとかぶっていた場合は、それ以外をstateにセットする、またかぶっていなければそのままstateをセット
2.そしてselectGenreのstateがセットされるごとにデータをfetchする(ジャンルボタンをクリック)
今回は、公開中(/now_playing)、人気(/)、高評価(top_rated)、新作公開(upcoming)も同じコンポーネントで表示するのでパスによってfetchするときのURIが変わるようにしています。ページ遷移
//次にページをセット const prevPage = (page - 1) <= 0 ? 1 : (page - 1); //ページを一個戻る挙動 const nextPage = (page + 1) > total_pages ? total_pages : (parseInt(Page, 10) + parseInt('1', 10)); //そして次へボタンへのchangePageは、nextPageを戻るボタンへのchangePageは、prevPageを渡す const changePage = (page: number) => { if(page === 0){ alert('該当の作品はありませんでした。') return false }else{ if(typeof(Storage) !== 'undefined'){ localStorage.setItem('currentPage', JSON.stringify(page)) } const GenresID = selectGenre.map((g:genre) => g.id) if(path === '/'){ dispatch(fetchMovieList(API_GET_MOVIE_POPULAR, GenresID, page)) }else if(path === '/upcoming'){ dispatch(fetchMovieList(API_GET_MOVIE_UPCOMING, GenresID, page)) }else if(path === '/now_playing'){ dispatch(fetchMovieList(API_GET_MOVIE_NOW_PLAYING, GenresID, page)) }else if(path === '/top_rated'){ dispatch(fetchMovieList(API_GET_MOVIE_TOP_RATED, GenresID, page)) } } } return( <button type="button" title="Previous 20 movies" onClick={() => changePage(prevPage)} > Prev </button> <div> {page} <span> / </span> {total_pages} </div> <button type="button" title="Next 20 movies" onClick={() => changePage(nextPage)} > Next </button> )1.prevPageは、page数が1以外の場合は、pageステートを-1する
2.nextPageは、fetchしてきた情報の中にトータルのページ数も入っているので、そのトータルページ数とpageステートが同じではない場合は、pageを+1するようになっている。
3.このprevPageは、戻るボタンをクリックした時にchangePageの引数として渡す。次へボタンを押したらnextPageを下記のように引数として渡す。
4.前、次のページのボタンがクリックされるとpageステートがそれに応じて変化してそれを各pathの映画のfetchメソッドに渡すことでpageを戻ったり、次へ進んだりできるようになる。
- 投稿日:2020-10-23T21:15:54+09:00
3.TMDB データfetch
はじめに
1.アプリケーション紹介
2.認証機能
3.TMDB api fetch
4.auto-suggestion検索フォーム
5.ジャンルフィルターとページ移動
6.リスト いいね 通知今回は実際にapi叩く処理を載せていきます。
tmdbのapiは充実しているのでより詳しく映画の詳細の表示やフィルタリングができました。
どんなアプケーションかは、1回目の記事にて載せておりますのでみていただけると幸いです。コードがあればいいという方は、こちらからどうぞ!
概要
今回は、新作公開(upcoming)、人気(popular)、公開中(now_playing)、高評価(top_rated)を映画のリストを表示できるようにします。
それごとにジャンルのフィルターをかけられるようにしています。映画リストfetch
export const fetchMovieList = (API_GET_MOVIE_BY = API_GET_MOVIE_POPULAR, genreIDs: number[], page = 1) => { const genreParams = genreIDs ? `${API_PARAMS_GENRE}${genreIDs.join('%2C')}` : ''; return async (dispatch: any) => { dispatch(fetchMovie()) return fetch(`${URL}${API_GET_MOVIE_BY}${API_KEY}${API_PARAMS_PAGE}${page}${genreParams}`) .then(response => response.json()) .then(json => dispatch(fetchMovieSuccess(json.results, json.page, json.total_pages))) .catch(error => dispatch(fetchMovieFailure(error))) } } export const URL = 'https://api.themoviedb.org/3/'; export const API_GET_MOVIE_POPULAR = 'movie/popular'; export const API_GET_MOVIE_UPCOMING = 'movie/upcoming' export const API_GET_MOVIE_NOW_PLAYING = 'movie/now_playing' export const API_GET_MOVIE_TOP_RATED = 'movie/top_rated'こちらが、映画のジャンルのリストを叩く処理になります。
最初に引数であるAPI_GET_MOVIE_BYは、デフォルトだと人気popularのapiを叩くので、デフォルトはpopularのapiを設定しておきます。genresIDsは、ジャンル選択した時にgenreIDsを引数でわたします。page遷移も行いますので最初はデフォルトの1を設定しておきます。
https://api.themoviedb.org/3/movie/now_playing?api_key=API_KEY&page=1&with_genres=99%2C35
urlは上記の形で叩くのでgenresのパラメータでは、genreIdごとに%2Cを入れなければいけないので、genreParamsにて代入します。
fetchしている時、fetchが成功した時、失敗した時にそれぞれreducerに渡してstoreで更新します。映画検索fetch
export const searchMovieList = (keyword: string) => { let url = URL_SEARCH + keyword + API_KEY_ALT; return async (dispatch: any) => { dispatch(searchMovie(keyword)) return fetch(url) .then(response => response.json()) .then(json => json.results) .then(data => dispatch(searchMovieSuccess(data, keyword))) .catch(error => dispatch(searchMovieFailure(error))) } } export const URL_SEARCH = 'https://api.themoviedb.org/3/search/movie?query=';映画詳細関連fetch
export const fetchMovieDetail = (id: string) => { const url_movie = URL_DETAIL + id + API_KEY; return async (dispatch: any) => { dispatch(fetchMovieDetailAction()) return fetch(url_movie) .then(response => response.json()) .then(data => dispatch(fetchMovieDetailSuccess(data))) .catch(error => dispatch(fetchMovieDetailFailure(error))) } } export const URL_DETAIL = 'https://api.themoviedb.org/3/movie/';まずここでは、映画のidを使って映画の詳細取得します。
そしてその取得後の情報をもとに、youtubeの関連動画、俳優リスト、関連映画の取得を行います。export const fetchTrailerList = (id: string) => { const url_trailers = URL_DETAIL + id + URL_VIDEO + API_KEY; return async (dispatch: any) => { dispatch(fetchTrailers()) return fetch(url_trailers) .then(response => response.json()) .then(json => json.results) .then(data => { let youtubeTrailers = data.filter((trailer:any) => { return trailer.site === 'YouTube'; }) dispatch(fetchTrailersSuccess(youtubeTrailers)); }) .catch(error => dispatch(fetchTrailersFailure(error))) } } export const fetchCastList = (id: string) => { const url_casts = URL_DETAIL + id + URL_CAST + API_KEY; return async (dispatch: any) => { dispatch(fetchCasts()) return fetch(url_casts) .then(response => response.json()) .then(json => json.cast) .then(data => dispatch(fetchCastsSuccess(data))) .catch(error => dispatch(fetchCastsFailure(error))) } } export const fetchSimilarMovies = (movieID: string) => { let url = URL + API_GET_MOVIE_SIMILAR(movieID) + API_KEY return async (dispatch: any) => { dispatch(fetchMovie()) return fetch(url) .then(responnse => responnse.json()) .then(json => json.results) .then(data => dispatch(fetchMovieSuccess(data, 0, 0))) .catch(error => dispatch(fetchMovieFailure(error))) } } export const URL_VIDEO = '/videos'; export const URL_CAST = '/casts'; export const URL_DETAIL = 'https://api.themoviedb.org/3/movie/'; export const URL = 'https://api.themoviedb.org/3/'; export const API_GET_MOVIE_SIMILAR = (movieID: any) => `movie/${movieID}/similar`;tmdbのapiでは、関連の予告動画などやキャストや似ている映画などのapiも続けて叩けるようになっているので、それをつかって
より詳しく映画の詳細を表示できるようになっています。
映画の詳細はこんな感じです!俳優詳細fetch
export const fetchActorDetail = (id: string) => { const url_actor = URL_PERSON + id + API_KEY; return async (dispatch:any) => { dispatch(fetchActor()) return fetch(url_actor) .then(response => response.json()) .then(data => dispatch(fetchActorSuccess(data))) .catch(error => dispatch(fetchActorFailure(error))) } } export const URL_PERSON = 'https://api.themoviedb.org/3/person/';actorIdを使って俳優の詳細を取得します。
そしてその俳優の出演作品の情報を取得しますexport const fetchActorMovieList = (id:string) => { let url: string; if(id)url = URL_LIST + API_KEY + '&with_cast=' + id; else url = URL_LIST + API_KEY; return async (dispatch:any) => { dispatch(fetchMovie()); return fetch(url) .then(response => response.json()) .then(json => json.results) .then(data => dispatch(fetchMovieSuccess(data, 0, 0))) .catch(error => dispatch(fetchMovieFailure(error))) } } export const URL_LIST = 'https://api.themoviedb.org/3/discover/movie';こんな感じで表示します。デザインはTMDBに似せております。
ページのデザインやなどはgithubのコードにて確認できます。
ここでは省かせていただきます。
- 投稿日:2020-10-23T21:14:18+09:00
2.TMDB 認証機能とページ構成
はじめに
1.アプリケーション紹介
2.認証機能
3.TMDB api fetch
4auto-suggestion検索フォーム
5.ジャンルフィルターとページ移動
6.リスト いいね 通知今回認証機能についての記事になります。
firebaseを使って実装しました。
以下が認証用ページになります。サインアップ
export const signUp = (username:string, email:string,genres:Array<{id: number, name: string}>, password:string, confirmPassword:string) => { return async (dispatch:any) => { if(username === "" || email === "" || password === ""){ alert('必須項目が未入力です') return false } if(password !== confirmPassword){ alert('パスワードが一致しません') return false } return auth.createUserWithEmailAndPassword(email, password) .then(result => { const user = result.user if(user){ const uid = user.uid const timestamp = FirebaseTimestamp.now() const userData = { uid: uid, genres: genres, email: email, username: username, created_at: timestamp, } usersRef.doc(uid).set(userData) .then(() => { dispatch(push('/signin')) }) .catch(error => alert('通信環境を整えて再度試して下さい。')) } }) } }認証機能と同時にfirestoreのuserコレクションにユーザーデータをセットするようにしております。
バリデーションは、他にもあると思いますが、まだ実装できておりません。
メールの形式などでのバリデーションもするべきなのでのちに実装していこうと考えております。サインイン
export const signIn = (email: string, password: string) => { return async (dispatch: any) => { if(email === "" || password === ""){ alert('必須項目が未入力です') return false } auth.signInWithEmailAndPassword(email, password) .then(result => { const user = result.user if(user){ const uid = user.uid db.collection('user').doc(uid).get() .then(snapshot => { const data:any = snapshot.data() dispatch(signInAction({ isSignedIn: true, username: data.username, uid: data.uid, genres: data.genres, })) dispatch(push('/')) }) } }) } }リセットパスワード
export const resetPassword = (email:string) => { return async (dispatch: any) => { if(email === ""){ alert('必須項目が未入力です') return false }else{ auth.sendPasswordResetEmail(email) .then(() => { alert('入力されたアドレスにパスワードリセット用のメールを送信しました。') dispatch(push('/signin')) }) .catch(() => { alert('メールの送信に失敗しました。通信環境を整えて再度試してください。') }) } } }サインアウト
export const signOut = () => { return async (dispatch: any) => { auth.signOut() .then(() => { dispatch(signOutAction()) }) .catch((error) => { console.log(error) }) } }Authコンポーネント
useEffect(() => { if (!isSignedIn) { dispatch(listenAuthState()) } },[]) if(!isSignedIn){ if(path === '/mylist'){ dispatch(push('/')) return children } return children }else{ if(path === '/signin'){ dispatch(push('/')) } return children }pathは、パスネームになります。/mylistは、ログインしないと入れないページなのでそのページに入ると違うページへ、Redirectされるようになっております。
そして自動ログインのリッスンのコードはこちらです
export const listenAuthState = () => { return async (dispatch:any) => { return auth.onAuthStateChanged(user => { if(user){ const uid = user.uid usersRef.doc(uid).get() .then(snapshot => { const data = snapshot.data() if(!data){ throw new Error('ユーザーデータが存在しません') } dispatch(signInAction({ isSignedIn: true, username: data.username, uid: uid, genres: data.genres, })) }) } }) } }今回、reduxを使っておりますので、actionにデータを渡すようにしております。
ルーティング
<Provider store={store}> <ConnectedRouter history={history}> <Switch> <AuthWrapper> <Header /> <Route exact path="/signup" component={SignUp}/> <Route exact path="/signin" component={SignIn}/> <Route exact path="/reset" component={Reset}/> <Route exact path="/" component={MovieContainer}/> <Route exact path="/upcoming" component={MovieContainer}/> <Route exact path="/now_playing" component={MovieContainer}/> <Route exact path="/top_rated" component={MovieContainer}/> <Route path="/search(/:keyword)?" component={MovieContainer}/> <Route path="/movie(/:id)?" component={MovieDetail}/> <Route path="/actor(/:id)?" component={Actor}/> <Route exact path="/mylist" component={MyList}/> </AuthWrapper> </Switch> </ConnectedRouter> </Provider>,今回のページ構成です。先ほどのAuthコンポーネントがAuthWrapperになります。
- 投稿日:2020-10-23T21:12:57+09:00
1.TMDB apiを使ったアプリケーション紹介
はじめに
1.アプリケーション紹介
2.認証機能
3.TMDB api fetch
4.auto-suggestion検索フォーム
5.ジャンルフィルターとページ移動
6.リスト いいね 通知今回TMDBのapiを使って映画検索のアプケーションを作ってみました。
cssの勉強不足なのでデザインに関しては、TMDBのアプリケーションをまねて作らせていただきました。
こちらになります。
プログラミング勉強したての初心者の制作物になりますので、暖かくみていただけると幸いです。
githubにて公開もしておりますので、コードのみ知りたい方は以下からどうぞ!!
https://github.com/yuuki008/movie-box今回は、単にどういったアプケーションかの紹介になります。
主な機能紹介
ジャンルボタンをクリックすることで、TMDBapiを叩いて映画を取得できるようにします。
人気だけでなく、公開中、高評価、新作公開のapiもあったので、それごとにapiに上記のようなジャンルフィルターに掛けられるようにしています。
つぎに認証機能にログインするといいねとリスト作成、追加、削除ができます。
映画の検索フォームに入力すると1文字ごとにapiを叩き、検索結果が出力されるようになっています。
以上が大体のアプリ紹介になります。
使ったパッケージ
"@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/styles": "^4.10.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "connected-react-router": "^6.8.0", "firebase": "^7.21.0", "history": "^4.10.1", "react": "^16.13.1", "react-dom": "^16.13.1", "react-notification-system": "^0.4.0", "react-redux": "^7.2.1", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-scripts": "3.4.3", "redux": "^4.0.5", "redux-action": "^1.2.2", "redux-logger": "^3.0.6", "redux-thunk": "^2.3.0", "reselect": "^4.0.0", "router": "^1.3.5", "thunk": "0.0.1"ディレクトリ構成
├── AuthWrapper.tsx ├── api.tsx ├── assets │ ├── actor.css │ ├── genreList.css │ ├── images │ │ ├── logo.svg │ │ ├── logo_square.svg │ │ ├── no_image.png │ │ ├── themoviedb.png │ │ └── themoviedb_green.svg │ ├── movieDetail.css │ ├── pageButton.css │ ├── profile.css │ └── search.css ├── components │ ├── Card │ │ ├── Cast.tsx │ │ ├── DefaultCard.tsx │ │ ├── MovieCard.tsx │ │ ├── MovieCard2.tsx │ │ └── Trailer.tsx │ ├── Modal │ │ └── FolderList.tsx │ ├── PageComponent │ │ ├── Favorite.tsx │ │ ├── FolderMovie.tsx │ │ ├── Genre.tsx │ │ ├── Header.tsx │ │ └── Release.tsx │ ├── UIkit │ │ ├── BoxLabel.tsx │ │ ├── FormControl.tsx │ │ ├── LightTooltip.tsx │ │ ├── MenuButton.tsx │ │ ├── Notification.tsx │ │ ├── PageButton.tsx │ │ ├── PrimaryButton.tsx │ │ ├── RatingStar.tsx │ │ ├── ReleaseMovie.tsx │ │ ├── SelectBox.tsx │ │ ├── Suggestion.tsx │ │ ├── TextInput.tsx │ │ └── index.tsx │ └── index.ts ├── containers │ ├── Actor.tsx │ ├── Auth │ │ ├── Reset.tsx │ │ ├── SignIn.tsx │ │ ├── SignUp.tsx │ │ └── index.tsx │ ├── MovieContainer.tsx │ ├── MovieDetail.tsx │ ├── MyList.tsx │ └── index.ts ├── firebase │ ├── config.tsx │ └── index.tsx ├── index.css ├── index.tsx ├── react-app-env.d.ts ├── redux │ ├── actor │ │ ├── actions.tsx │ │ ├── operations.tsx │ │ └── reducers.tsx │ ├── castlist │ │ ├── actions.tsx │ │ ├── operations.tsx │ │ └── reducers.tsx │ ├── folder │ │ ├── actions.tsx │ │ ├── operations.tsx │ │ └── reducers.tsx │ ├── movie │ │ ├── actions.tsx │ │ ├── operations.tsx │ │ └── reducer.tsx │ ├── movielist │ │ ├── actions.tsx │ │ ├── operations.tsx │ │ └── reducers.tsx │ ├── selectors.tsx │ ├── store.tsx │ ├── trailerlist │ │ ├── actions.tsx │ │ ├── operations.tsx │ │ └── reducers.tsx │ └── user │ ├── actions.tsx │ ├── operations.tsx │ └── reducers.tsx ├── serviceWorker.ts └── setupTests.tsこちらがpackage.jsonの中身になります。
認証機能とdatabase管理は、firebaseを使いました。
UI構築は、material-uiをふんだんに使いました。
今回は、typescriptを使いましたが、まだ勉強し始めのため、any型を多く使ってしまっております!!
申し訳ないです!!終わりに
今回は、主な機能の実装方法しか載せておりませんので、コードを全て細かく知りたい方はこちらからどうぞ!
ではあと4つ記事を上げていきますので、みていただけると幸いです。
- 投稿日:2020-10-23T17:31:22+09:00
フロントエンドとバックエンドを同時に開発している際の CORS 問題を解消する
はじめに
研究で使用するプロトタイプを React.js と NestJS を使用して開発していた際、CORS が発生して大変だったので共有しておきます。
結論
"proxy": "設定したい URL"
をpackage.json
にプロキシ設定を記述すると解決できます。例
package.json{ ... , "proxy": "http://localhost:4000" }と記述すると fetch などでリクエストを投げる際に
http://localhost:4000/api/v1/
の代わりに/api/v1/
で実行が可能になります。まとめ
CORS で悩まされて、毎回ものすごく困っていたので機能を見つけたとき感動しました。
初歩的な問題で知っている方も多いと思いますが、少しでも参考になればと思います。参考文献
- Proxying API Requests in Development - https://create-react-app.dev/docs/proxying-api-requests-in-development/
- ReactでAPIリクエストをプロキシする / Proxy API request with React - https://medium.com/@dorayakikun/react%E3%81%A7api%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9%E3%82%92%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7%E3%81%99%E3%82%8B-proxy-api-reference-with-react-d5108c181e28
- 投稿日:2020-10-23T16:27:51+09:00
Reactひと言メモ(hook)
hookとは
クラスコンポーネントでしか扱えなかったライフサイクル(useEffect)、状態管理(useState)を関数コンポーネントでも扱えるようにしたもの。
useContextもあるよ(Redux使わないときに使うかも)
インポートの仕方
import React , {useEffect, useState} from 'react'useState
//以下のような1行で設定できる。 const [変数名, 関数名] = useState(初期値) const [abc, setabc] = useState("") //ステートabcに文字列aaaをセットできる。 setabc("aaa")useEffect
useEffect(() => { //ここに処理 },[依存値])
- 投稿日:2020-10-23T16:08:22+09:00
Reactリンク集
アコーディオンの作成
https://qiita.com/someone7140/items/791256619f2b46365ec4
モーダルの作成
https://mebee.info/2020/04/09/post-6894/
ファイルのアップロードの作成
- 投稿日:2020-10-23T16:04:14+09:00
React 学習帳その1
React とは?
React はユーザインターフェイスを構築するための、宣言型で効率的で柔軟な JavaScript ライブラリです。複雑な UI を、「コンポーネント」と呼ばれる小さく独立した部品から組み立てることができます。
↑公式
JSX
Reactで表示したいときに使うものは、JSXという。
Javascriptの拡張した書き方?らしい。returnでJSXを返して、returnの外ではJavascriptを書ける。
JSX内にJavascriptを埋め込むこともできるJavascript部分を{}で囲む
JSXはreturnの中に複数の要素があるとエラーになるので、で一つの要素にまとめてあげる。imgタグに閉じタグ/
<img src='画像のURL' />
//Reactをインポート import React from 'react'; //React.componentを継承するクラスですよー class App extends React.Component { //JSXを戻り値とするrenderメソッド render() { const imgUrl = "画像URL" return ( <div> <img src={imgUrl}/> </div> );お気づきだろうか・・・・
{imgUrl}
を""
で囲っていないことに・・・。
なぜかはわからないが囲むとエラーだった。いつかわかるといいな。
- 投稿日:2020-10-23T14:51:01+09:00
eslint の設定で import をきれいにする
はじめに
複数人で開発する際に「コーディングルールを揃えたい」というケースはよくあります。
各々が自由に開発していると、import の順番がバラバラになったりして、プルリクエストの際に本質的ではない変更が混在します。私が香港のスタートアップで開発していたときは、チームメンバー全員が VSCode を利用しており、settings.json に
"editor.codeActionsOnSave": { "source.organizeImports": true, },を入れることで、統一していました。
しかし、エディタ(は宗教問題なので)を統一したくないケースもあると思いますので、 eslint で出来ればベターだと思います。今回は、その方法をご紹介します。
実装
今回ご紹介するプラグインは
import
,unused-imports
です。
まずはプラグインをインストールします。yarn add -D eslint-plugin-import eslint-plugin-unused-imports
次に
.eslint.yaml
を下記のように変更します。# .eslint.yaml env: browser: true es2021: true extends: - 'plugin:prettier/recommended' - 'prettier/@typescript-eslint' - 'prettier/react' parser: '@typescript-eslint/parser' parserOptions: ecmaFeatures: jsx: true ecmaVersion: 12 sourceType: module plugins: - react - '@typescript-eslint' - import rules: sort-imports: 0 "import/order": - warn - groups: - builtin - external - internal alphabetize: order: asc筆者の環境では VSCode の設定に、
"editor.codeActionsOnSave": { "source.fixAll.eslint": true },が入っているので、ファイルを変更して保存した瞬間に import の順番が変わります。
修正前
修正後
しかし、このままでは不要な(利用していない)import が残っています。
これも自動で消したいです。その設定を入れていきます。
# .eslint.yaml ... plugins: ... - unused-imports rules: ... "@typescript-eslint/no-unused-vars": off unused-imports/no-unused-imports-ts: warn ...修正後
不要な import が消えました。
おわりに
複数人で開発する時に、コードフォーマッタや elinter で記述ルールを揃えると開発速度が一気に加速します。
今回紹介した方法よりも良い方法や、他にも記述改善するためのテクニックがあればぜひ教えてください。
- 投稿日:2020-10-23T14:13:09+09:00
React Hooks と TypeScript で子コンポーネントに state を渡す方法まとめ
ReactとTypeScriptを使った子コンポーネントにstateを渡す方法をまとめてみました。
別に"Reactと表記するだけ"でも良いのでは?と思うかもしれませんがコードのサンプルを載せることでTypeScript込みの労力みたいなところを感じ取ってもらえればと思いあえて React x TypeScript として取り上げています。
①ダイレクト
②関数経由
③useContext(createContext)上記のようにおそらくメジャーな手法の3パターンがあります(他の方法などコメントもらいタイ)。
①ダイレクト
stateをそのまま子に渡す方法です。状態を変更するsetStateでコードの方でサンプル載せました。
stateProps.tsximport React, { FC, useState } from "react"; import Child from "./Child"; const Parent: FC = () => { const [stateProp, setStateProp] = useState<string>("Stateです"); return ( <> <h1>親です</h1> <div>{stateProp}</div> <Child setStateProp={setStateProp} /> </> ); }; export default Parent;あえてそのまま受け取った子コンポーネントを載せました。
stateChildSample.tsximport React, { FC } from "react"; const Child: FC = ({ setStateProp }) => { return ( <> <h2>Childです</h2> <button onClick={() => setStateProp("更新した")}>Stateを更新する</button> </> ); }; export default Child;setStateProp をそのまま使えそうですが当然ながらPropsに型を付けないと以下のように怒られます。
Type '{ setStateProp: Dispatch<SetStateAction<string>>; }' is not assignable to type 'IntrinsicAttributes & { children?: ReactNode; }'. Property 'setStateProp' does not exist on type 'IntrinsicAttributes & { children?: ReactNode; }'.ts(2322)要するに Dispatch< SetStateAction< string > > で型を付けなさいということです。
ちなみに Dispatch と生成された SetStateAction の型はimportしてこないといけません。下のようなコードで更新できるようになりました。
stateChildSample改.tsximport React, { FC, Dispatch, SetStateAction } from "react"; const Child: FC<{ setStateProp: Dispatch<SetStateAction<string>> }> = ({ setStateProp }) => { return ( <> <h2>Childです</h2> <button onClick={() => setStateProp("更新した")}>Stateを更新する</button> </> ); }; export default Child;codesandbox: https://codesandbox.io/s/state-typescript-update-kn8kj?fontsize=14&hidenavigation=1&theme=dark
デメリット
お手軽ですが型付けが少しややこしいと思いました。
またReact公式チュートリアルでもstateはできる限り親の方でキープした方がスッキリかけて単一方向のデータフローの原則を守りやすいと思います。②関数経由
ポピュラーな方法だと思います。
funcParentSample.tsximport React, { FC, useState } from "react"; import Child from "./Child"; const Parent: FC = () => { const [stateProp, setStateProp] = useState<string>("Stateです"); const updateState = (): void => setStateProp("更新した"); return ( <> <h1>親です</h1> <div>{stateProp}</div> <Child updateState={updateState} /> </> ); }; export default Parent;型付けも関数の型を渡してあげます。
funcChildSample.tsximport React, { FC } from "react"; const Child: FC<{ updateState: () => void }> = ({ updateState }) => { return ( <> <h2>Childです</h2> <button onClick={() => updateState()}>Stateを更新する</button> </> ); }; export default Child;codesandbox: https://codesandbox.io/s/func-state-update-b6ohz?fontsize=14&hidenavigation=1&theme=dark
③useContext(createContext)
useContextとはpropsを使わずに子から孫まで値を渡すことができるHooksの一つです。親より以下のグローバル変数といったイメージとも言い換えられます。
親側は "react" ライブラリから { createContext } を引っ張っています。子や孫側では { useContext } を引っ張ります。
親側ではcreateContextを格納した変数をコンポーネントとして使いますが < Context.Provider > といった形で .Provider で値をJSX上に呼び出すことができます。
parent.tsximport React, { FC, createContext, useState } from "react"; import Child from "./Child"; export type ContextType = string; export const Context = createContext<ContextType>(""); //ここで初期化 const Parent: FC = () => { const [state, setState] = useState( "私はContextです。propsで渡してもらっていません。" ); const updateContext = () => setState("Contextを更新したよ。"); return ( <> <h1>親です</h1> <button onClick={updateContext}>contextを更新するボタン</button> <Context.Provider value={state}> <Child /> </Context.Provider> </> ); }; export default Parent;次は子コンポーネントですが、孫(ChildChild.tsx)、ひ孫(ChildChildChild.tsx)もほぼ同じコードになっており { useContext } をReactライブラリから引っ張ってきています。
加えて import { Context } from "./Parent"; によってcreateContextで初期化した変数を引っ張っており、 { useContext } と一緒に使います。
child.tsximport React, { FC, useContext } from "react"; import ChildChild from "./ChildChild"; import { Context } from "./Parent"; const Child: FC = () => { const ChildContext = useContext(Context); return ( <> <h2>Childです</h2> <p>{ChildContext}</p> <ChildChild /> </> ); }; export default Child;親側でクリックするとpropsで渡していないですが子にも反映されるようになります。
<button onClick={updateContext}>contextを更新するボタン</button>codesandbox:https://codesandbox.io/s/props-usecontext-6x1dj?fontsize=14&hidenavigation=1&theme=dark
useContextに関しては以前、5分でわかる useContext の使い方【TypeScriptまで】の記事で上記コードをさらに噛み砕いて詳細に解説しています。
useContextのデメリット
propsリレーの煩わしさや複雑化の解決策になります。一方で子コンポーネントから変更を加えたいケースでは工夫が必要となるため、①や②の手法の方が手っ取り早いです。
子からstateを変更したい場合は【React + Typescript】useContext の値を子コンポーネントから更新が参考になります。まとめ
基本的には関数かuseContextが無難かと。他のHooksとの兼ね合いや複雑さに応じて渡し方を最適化できていきたいです。
- 投稿日:2020-10-23T08:42:11+09:00
React学習日記② ~Firebaseへのデプロイ~
はじめに
今回は、create-react-appで作成したReactアプリをFirebaseへデプロイ際の手順を簡単にまとめます。
※こちらの記事はReact初学者の備忘録・アウトプットを目的とした記事です。理解が浅い部分もありますが、ご容赦ください。何かしら参考になれば幸いです。
そもそもFirebaseとは
- FirebaseとはGoogleが提供するアプリのプラットフォームのことです。様々なプロジェクトのデータベースがこのクラウド環境に設置されているわけです。
- Firebaseはクラウド環境にデータベースなどを設置して、インターネット経由でアクセスして利用できるようにしています。サーバー側にプログラムを用意する必要がないため、Reactのようなクライアント側だけしかもたないWebアプリでも使用されます。
開発手順
01. Firebaseにプロジェクトを作成する。
02. リソースのロケーションを設定する。
クラウドリソースのロケーションに「asia-northeast1」に設定します。
03. 使用するプラットフォームを選択
04. データベースを作成
- それぞれのアプリにあったデータベース("cloud fire store"か"real time database")を選択するのですが、公式ドキュメントを参考にしてください。
- 今回はfire storeを利用するので、サイドバーから、cloud fire storeを選択します。
本番モードかテストモードかを聞かれますが、firestore.rulesというファイルで設定を上書きするので、どちらでもいいです。
05. 各コマンドの実行
- firebase toolsをグローバルインストールします。ターミナルで以下のコマンドを実行します。(一度でも実行したことがあれば、実行する必要はありません)
% npm install -g firebase-tools
- firebaseをインストール
% npm install —save firebase
- firebaseにログイン
% firebase logingoogleアカウントを選択して許可します。
06.firebase initを設定
% firebase initWitch Firebase CLI features do you want to set up for this folder?と聞かれますが、今回は
◯Firestore
◯Functions
◯Hosting
にチェックを入れます。
(上下の方向キーで移動して、スペースキーでチェックを入れます。選択したEnterキーで次へ進みます。)
- 続いてPlease select an option:と聞かれます
今回はプロジェクトを作ってあるので、
use an existing project(既存のプロジェクトを使う)を選択して、プロジェクトを選択します。
What file should be used for Firestore Rules?(firestoreのルールを決めるファイルはこれでいいですか?)と聞かれるので、デフォルトでEnterを押します。
What file should be used for Firestore indexes?(firestoreのindexを決めるファイルはこれでいいですか?)と聞かれるので、こちらもyesで通します。
What langage would you like to use to write Cloud Functions?(cloud functionsをどの言語で書きますか?)と聞かれるので、JavaScriptかtypeScriptかを選択してEnterを押します。(今回はTypeScriptを選択しました。)
Do you want to use TSLint to catch probable bugs and enforce style?と設定を聞かれるので、yesで通します。
Do you want install dependencies with npm now?(npmで依存関係のあるものをインストールしますか?)と聞かれるので、yesで通します。
What do you want to use as your public directory?(どのパブリックディレクトリをつかいますか?)と聞かれます。
create-react-appでは、後ほど生成するbuildファイルを本番環境用に用意するので、「build」を指定してEnterを押します。Configure as a Single-page app?(SPAとして設定したいですか?)と聞かれますが、SPAを作る人はyesをにしてEnterを押します。
07. firestore.rulesの設定
- エディタに戻り、firestore.rulesファイルを開き、最低限のセキュリティ設定を行います。
firestore.rulesrules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read; allow write: if request.auth.uid != null; } } }こちらは、誰でもデータベースを読み込むことはできるが、書き込みは認証されたユーザーでないとできないという設定を加えることができました。
buildフォルダを生成
- 本番用のbuildフォルダを生成するために、以下のコマンドを実行します
% npm run build
- コンパイルエラーを防ぐためにfunctoins/src/index.jsのimportを一旦コメントアウトします。
functions/src/index.ts// import * as functions from 'firebase-functions';
08. Firebaseへデプロイ
- ターミナルへ戻り、以下のコマンドを実行します。
% firebase deploy成功すると、下の方にHosting URLというものが現れますが、こちらが本番環境のURLになります。
firebase deploy時のエラー解決
deploy時に400番のエラーを返されることがあります。
こちらは、firebaseの料金プランが違うためです。firebaseの料金プランを無料プランから、Blaze(従量課金制)に変更しなくてはなりません。基本的に個人で開発する程度のデータ量なら、無料で使えるはずですが、公式ドキュメントを参考にしてプランを変更してみてください。おわりに
大分冗長な説明になってしまいましたが、最後までお付き合いくださりありがとうございました。
修正点等ありましたら、教えてくださると助かります。
- 投稿日:2020-10-23T00:42:07+09:00
シャニマスのカードを検索できるサイトを作ってちょっとバズったけど公式に検索機能がついて終わった話
アイドルマスターシャイニーカラーズ(シャニマス)遊んでますか?
5月末に公開した「シャニマスsSSR検索」について、数ヶ月経過しましたがせっかく作ったので顛末(?)の記事を書こうと思います。
コードらしいコードはでてきません。
(ゲームに直接関与するツールではありませんが、権利的にグレー?な部分があると思います。連絡を取りたい権利者の方はTwitter@nok0714にDMをください)サイトはこちらです↓
https://shiny-support-search.netlify.app/背景
シャニマスは、2018年の4月24日にサービスを開始したアイドルマスター(アイマス)シリーズの末っ子です。
ソシャゲ・音ゲーのスタイルを取る他のスマホ向けアイマスとは異なり、アイドル育成&ライブ対戦(公式のジャンル説明)ゲームとなっています。
ジャンルの通りゲームは大まかに2パートに別れています。
- アイドル育成: プロデュースでシナリオを見つつ好きなキャラを育てる
- ライブ対戦: ↑で育てたキャラを5人選んで編成したユニットで、他のプレイヤーのユニットと対戦する
2.では3人の審査員(Vo/Da/Vi)に効果的なアピールをたくさん行ったほうが勝ち(ざっくり)というスタイルになっています。
効果的なアピールをするには、自分の立てた戦略をうまく回せるような編成が必要になります。そのためには1.の育成でいかに強いアイドルを育てられるか、が重要になります。
1.の育成パートでは、育成対象のキャラ(プロデュースアイドル)に対して、5人の補助キャラ(サポートアイドル・ゲストアイドル)を参加させられます。サポートアイドルにはそれぞれスキルが設定されています。例↓
- 一緒にダンスレッスンをするとダンスのステータスがたくさん伸びる「ダンスマスタリーDa」
- おやすみ(体力回復)のときにたくさん回復する「おやすみブースト」
- 同じユニットのキャラが同じレッスンやお仕事にいるとメンタル(対戦時の体力)が伸びる「ユニットマスタリーMe」
シナリオを勝ち抜いたり、強い対戦用アイドルを作ったりするには、このスキルをうまく編成することが重要な要素のひとつになります。
スキルはカードごとに違うものが設定されています。
シャニマスでは現在6ユニット・23人のキャラがいます。
ユニットの登場時期によって異なりますが、各キャラに10枚程度はカードがあります(レアリティSSRとSRを合わせるとこれくらいかと)。2020年5月当時でサポートSSRカードは90枚ほどありました。
また、当時は新シナリオであるG.R.A.D.が実装され、初期シナリオの W.I.N.G.編、1周年時に実装されたファン感謝祭編の3本立てとなったところでした。
それぞれのシナリオでどのようなスキルが重要になるかが異なり、ファン感謝祭とG.R.A.D.ではスキル以外にアイディア、ひらめきと呼ばれる要素が追加されました。
(例: オーディションを行わないファン感謝祭では、オーディションに勝つと特定のステータスが追加で伸びるオーディションマスタリーは無駄)当時はシャニマス側にキャラの検索機能がなく、いい加減覚えられないなーと思った & 2周年で入ってきた初心者さんが「あのスキルは誰が持ってるんだー!」と困っているだろうと考えたので、Reactの勉強も兼ねて、指定したスキルを持つカードを検索できるWebアプリを書いてみることにしました。
(Twitterで「こんなんあったら便利かなー」って言ったらフォロワーに後押しされたので、それも原動力になりました)実装
React使おう
今どきっぽいWebの書き方は全然知らなかったのですが、自分が管理に関わるサイトで使っているGatsbyを見て「おお~こうやるのか~」と思ったので、Reactをベースにしてみることにしました。
で、どうもReactで何かを作るならCreate-react-appがいいらしいと聞き、これを使ってみることにしました。
Create-react-appでは、最低限の環境をお膳立てして、Reactを使ったWebアプリを製作できるようにしてくれます。で、なるべくなら画面をかっこよくしたいということで、UIフレームワーク的なものも入れてみることにしました。
コンポーネントの充実具合(特に"カード"が良かった!)でMaterial UIを選びました。まずはなにか表示できないかなと思って、適当に作ってみたのがこれ。
最初は、上の方の画像で示したプロデュースユニットを再現しようとしていました。同じものが15枚並んでいますが、個々のものはカードのコンポーネントとチップのコンポーネントを組み合わせて作りました。
このあたりで、「自前で好き勝手にコンポーネントを組み合わせて部品として名前をつけて自由に使えるってスゲー!」ってなってました。
ついでに、npm install
で欲しい物を持ってこられるのもすごいし、ソースを変えればホットリロードされるのもすごい!!って喜んでました。
あとなにかしくじったときに意外と丁寧にエラーを教えてくれて「意外とやるやん」となっていました。画面構成を考える
↑と前後して「どういう画面構成にするか」について考え始め、しばらくお絵かきをしました。
変遷はこんな感じでした↓
その1 その2 その3 「その1」はシャニマスのプロデュースユニット編成画面とサポートアイドル一覧画面をガッチャンコしたようなものですね。
「その2」の上側では2枚のカードを比較する構成でした。下側でかなり画面が固まってきて、左ペインで持っているスキルを選択し、右ペインで検索結果を表示する形となっています。
実際、上のプロトタイプはその1とその2の途中くらいで作ったものだと思います。
で、結局「その3」(落書きは無視で…)で画面構成が固まりました。本格的に作る
半年近く前なのでもうどういう順序で作ったか覚えていませんが、左ペイン・右ペインをページ全体で包む構造にしました。
実際にはMaterial UIのサイトを眺めてDrawerが使えそう!ということでこれを使いました。で、順にお絵かき通りのものを作っていきました。
- 右ペイン
- カード: プロトタイプのものを引っ張ってきて、
props
で渡したデータを表示できるように。- カードを並べる部分: Gridコンポーネントに
props
でカード一覧を入れられるように。- 左ペイン
- 感謝祭アイディア・G.R.A.D.ひらめき: Radioを使用。
- マスタリー・その他スキル: カードに使ったChipsをクリックできるようにして表示。
ガワはできたのですが、
props
とstate
とイベントについてよくわかっていなかったので、左側で絞り込んだスキルをどうやって右側に渡すんだろうとしばらく悩んでいました。
「スキルの絞り込みパネルでonClick
はつけられるけどどうやって親にこれを渡すんだ?」「親から渡ってきたとして右側ってもう描画されてるはずじゃんどうやって変更するの???」みたいな状態でした。結局色々やっているうちに「親が
props
で送りつけた関数を子のonClick
内で叩くのか」「React.useState
で作った変数をprops
として渡して、それに合わせて個々のカードの描画するしないを決めれば勝手に描画し直すんだ」とわかりました。
完全に雰囲気で書いているので合っているのか本当にわかりませんが、とりあえず動きました。そうしてできた初期のスクショがこちら。
この時点でデータがかけらも入っていなかったので、画像を集めつつ(持っていなかったカードの画像を提供してくれた友達に感謝)、シャニマス攻略Wiki(wikiwikiのやつ)のデータにお世話になりつつ、表示用データを整えてついに完成しました。
ファイルの作成日を見た結果、どうやら5月20日にプロトタイプをゴニョゴニョしはじめて、5月22日に本実装を始め、5月23日に公開用バージョンができいたようなので、かなり一気に書いたんだなあ。
公開
公開するからにはWebサーバが必要だぞ、となったのですが↑の実装で気力が尽きていて、自分で建てるのも嫌だったのでいまどきらしくNetlifyにおまかせしました。
作ったもの自体はすべてビルドバンドルで完結しており、動的に何かをする必要もなかったので(静的サイトって言うんですかね)Netlifyで十分でした。GitHubのプライベートリポジトリにコードをpushして、Netlifyでそのリポジトリと連携し、どのブランチをビルド対象にするか、ビルドコマンドはなにか等々を設定すればそれで完了だったので非常に楽でした。
(URLもサブドメイン部分なら好きに変えられるし!)そんなこんなで公開し、Twitterで作ったよ!ツイートを出しました。
それがこれ。結構カードも増えて「こんな性能の子いなかったっけ?」と探すのが大変なので、シャニマスのサポートカード検索機をつくってみました!SSRサポートを感謝祭アイデア・GRADひらめき・保有マスタリーで検索できます(が,スマホだと表示崩れします) https://t.co/dOSs7ivxsw pic.twitter.com/g0WaGsk0On
— モアイ|再生産 (@nok0714) May 23, 2020当時はシャニマス本家で一切キャラの絞り込みができなかったので、それなり(?)にウケました。
通知止まらんwを初めて体験しました。
ただ、イマドキのサイトなのにスマホ対応が全然できていなくて、そのあたりは失敗だったなあと思っています。
今は更新しているのでこうなりませんが、当時はデスクトップの表示がそのままスマホでも出ていて、width
の指定がテキトーだったので、検索にマッチするカードが3枚?を切ると表示がイカれていました↓
読み込み直後 マッチなし ゴミ化
RTやlikeをしてくれた人をちょくちょく覗いてみて「便利」「公式にくれ」って発言を見て鼻高々になったり、リプライで「便利ですね!」とお言葉をもらい天狗になっていたのですが、終焉はすぐに訪れました。
7月1日に「公式側に検索機能が実装される」という告知が行われたのです。ひと月少々の命でした。
実際に7月10日の更新で絞り込みができるようになり、サポートスキル(この記事でスキルと呼んでいるもの)・ライブスキル(サポートアイドルがオーディションに参加した際に利用できるスキル)・未読コミュの有無・登場種類(期間限定、イベント配布など)といった条件でキャラを探せるようになりました。
公開してるサイトより遥かに便利です。負け惜しみがこれ↓㊗シャニマス本体に絞り込みがついた㊗ので,サポートカード検索はお役御免です(短い一生だったな)
— モアイ|再生産 (@nok0714) July 10, 2020
サイト自体は開けたままにしておきますが,今後はシャニマス本体の絞り込みを使っていきましょう! #シャニマス https://t.co/0GBPjqiCOS pic.twitter.com/xGczyUE2Hkとはいえせっかく作ったのに放置するのももったいない。
ということで、自分が「ほしいな~」と思った機能を付け足すことにしました。機能追加
いまどんな感じかは https://shiny-support-search.netlify.app/ を見てください。
最初に公開したときにマスタリー以外での絞り込みの機能はないの?と訊かれていたので、その機能の更新から始めました。
機能を追加する、つまりgitのブランチを勉強できる!ということで、見様見真似でブランチを切ってみました。
master
: 公開用develop
: 開発用よろづfeature/x
: xの機能開発用hotfix/x
: なんか急務の更新用package-update
:npm
で入れているパッケージの更新data-update
: シャニマス側で新しいカードが追加された際のデータ追加で、細々したコード変更は
develop
で行い、それなりに量のありそうな作業はfeature
で行ってからdevelop
にマージしました。
我流でやったので合っているのかもよくわかりません。
結果、以下の更新を行いました。
- マスタリー以外のスキルで絞り込み(5/24): リプライで指摘されて。公開した最初のバージョンではマスタリーでしか絞り込めませんでした。実は準備はしていたので一瞬でした。
- スマートフォン対応(9/10): 小さい画面では検索条件ペインを開閉できるようにしました。3ヶ月以上開いていますが、この間は細かい表示調整をしていました。
- キャラ別の絞り込み(9/28): スキルの絞り込みと同じ形で、キャラ単位の絞り込みを実装しました。
- アコーディオンメニュー(9/28): 検索条件が増えてきたので、折り畳めるようにしました。キャラ別の絞り込みと同時です。
- OGPプレビュー機能(9/30): Twitterやfacebookでリンクをツイートするとリッチな感じになります。なかなかの曲者で、こいつのためにステージング環境を用意することになりました(それまではローカルで挙動を見て問題なさそうなら公開)。
- 画像だけモード(10/3): 右ペインのカード一覧からあえてスキルを非表示にできる。シャニマスの美しいイラストを眺めたい欲望で生まれました。
そんな感じで更新を続けて、途中でサイト名を変えたりしましたが、(これも適当ですが)初期公開でv0.1.0だったバージョンが10月22日現在ではv0.4.2になりました。
ちなみにスマホ対応直後はこんな感じで、メニューが開閉できます(カードの一覧性が下がった感じはしますが)
読み込み直後 メニューオープン時 このサイトの実装や更新を通して勉強できたこと
意外とまともに開発をしたことがなかったので、色々と勉強になりました。
- Reactを使ったフロントエンド?開発(ベタにHTMLを書くなんてもうできないなー)
- Material-UIを使ったUIづくり(スタイリングはあまりいじっていませんが、かなりコンポーネントが充実していて、組み合わせればいけそう感がよかったです)
- Netlifyを使ったCD/CI(git pushだけでいいんだから楽ちん)
- ちゃんとブランチを切ったGit開発(複数の機能を同時に、ということがなかったので結局一本道ですが)
今後も自分の思うままに更新していきたいなーという感じです。例えば
- 画像非表示モードで一覧性を上げる
- サービスワーカーを使ったオフライン動作
- サーバープッシュ(できるのだろうか?)
- スキル以外の性能でも検索
- シャニマス攻略Wikiへのリンク
- レアリティSRの追加(枚数が増えすぎるのであまりやりたくない)
- 内部のリファクタリング
- URLパラメタから検索条件を取得して、Twitterでのリンク共有から絞り込み済みの状態にアクセス(「どうしよー」「こん中から選べ ほらリンクだぞ」ってできたら便利そう)
などですかね。
最後に
かわいい(けどそれだけじゃない)キャラクター・美麗なイラスト・よく練られたストーリー
・引きやすいガシャにぶん殴られるアイドルマスターシャイニーカラーズをぜひ遊んでみてください。
PCブラウザでも遊べます。こちらから→ https://shinycolors.enza.fun/
スマートフォンアプリ版もあります。PCと同じセーブデータを使えるので、外ではスマホ・家でPCという感じでプレイ環境を変えられます!
P.S. ゲーム大好き・自分に自信が持てないけど変わりたい・双子のお姉ちゃん・顔が良い眠り姫こと大崎甜花ちゃんをよろしくおねがいします。
2020年10月22日現在、左下の【トゥインクル・トゥインクル】を入手できるイベント「ミルキィウェイ61-世界と世界がまじわる夜に-」が復刻開催されています。
これから始めるという方でも、プロデュースを2回終えると(早いシーズンで負けてしまってもOK)イベントポイントで1枚は入手できます!!!