- 投稿日:2020-11-15T23:33:38+09:00
サーバーレスな LGTM 画像生成サービスを作ってみた
作ったもの
ユーザー登録不要のシンプルな LGTM 画像生成サービスです。
ローカルから画像をアップロードして LGTM 画像を生成することができます。
GIF アニメーションにも対応しています。
画像検索機能を利用して好きな画像から LGTM 画像を生成することもできます。
生成された LGTM 画像の画像リンクを簡単にクリップボードにコピーすることができます。
リンクの形式は HTML と Markdown で好きな方を選択できます。
構成
バックエンドは ECS + Fargate と迷いましたが、小さく安く済ませたかったので API Gateway + Lambda の構成にしました。
フロントエンド
React + TypeScript で開発しました。
ビルドした静的ファイルをを S3 に配置して CloudFront 経由で配信しています。バックエンド
ServerlessFramework を使用して TypeScript + API Gateway + Lambda で開発しました。
実装について
LGTM 画像生成
ソースコード: lgtm-generator-api/lgtmWriter.ts
画像への文字入れをするのには gm を使用しています。
このライブラリは Node.js で GraphicsMagick もしくは ImageMagick を使用することができるライブラリです(今回は ImageMagick を使用しています)。
このライブラリを使用することで LGTM の文字入れ・画像のリサイズを簡単に実装することができました。
LGTM の文字のフォントには Archivo Black を使用しています。画像検索
ソースコード: lgtm-generator-api/imagesSearcher.ts
画像検索には Google の Custom Search JSON API を使用しています。
Node.js 製の Google API クライアントライブラリとしては googleapis というライブラリが公開されていますが、パッケージサイズが大きくて Lambda のデプロイパッケージのサイズが肥大化してしまったため今回は使用しませんでした。
その代わりに Http クライアントで cse.list メソッドを叩く処理を自前で実装しています。画像検索 API の設定 ~ 使用方法については以下のドキュメントを参考にしました。
- 投稿日:2020-11-15T23:01:53+09:00
【React環境構築】create-react-appでさくっと環境構築する手順
概要
今回は、JavaScriptのライブラリであるReactの環境構築を行なったので、メモ程度ですが情報を共有しときます。
環境構築はcreate-react-app
コマンドを使います。目次
開発環境
開発環境は以下の通りです。
- macOS Catalina
- Homebrew 2.4.0
- nodebrew 1.0.1
- Node.js v14.4.0
- zsh
- VScode
Reactの環境構築をする手順
今回、
create-react-app
を使って環境構築をしていきます。
Create React App
はReactの環境構築ツールで、コマンド1つでReactに必要な環境を構築することができます。
create-react-app
で環境構築するには、「Node.js」と「npm」が必要です。
また、これらをインストールするには、「Homebrew」と「nodebrew」が必要なので、順番にインストールしていきます。手順は以下の通りです。
- Homebrewをインストールする
- nodebrewをインストールする
- Node.jsをインストールする
- npmをインストールする
- create-react-appを実行する
1つずつクリアしていきましょう。
1. Homebrewのインストール
「Homebrew」とはmacOSまたはLinux用のパッケージマネージャーです。
Homebrewの公式サイトはこちらです。
Homebrewの公式サイトのホームページにあるコマンドをターミナルに貼り付けて実行します。
実行するとインストールが始まりますので、しばらく待ちます。
インストールが完了したら、以下のコマンドを実行してHomebrewのバージョンを確認してみましょう。このコマンドは、Homebrewのバージョンを確認するコマンドです。
% brew -v今回は、バージョン2.4.0がインストールされています。
このように、Homebrewのバージョンを確認することができたら、インストールは正常に完了しています。2. nodebrewのインストール
「nodebrew」とはNode.jsのバージョン管理システムで、Homebrewでインストールすることができます。
nodebrewはnodeをインストールするために使います。ターミナルで以下のコマンドを実行します。
% brew install nodebrewしばらく待ちます。
Homebrewをインストールした際と同様に、nodebrewのバージョンを確認します。
以下のコマンドを実行しましょう。% nodebrew -vこのようにnodebrewのバージョンが表示されれば、nodebrewのインストールは完了です。
3. Node.jsをインストールする
「Node.js」とは、簡単にいうとサーバーサイドで動くJavaScriptのことです。
Node.jsをインストールしていきます。
まず、インストール可能なNode.jsのバージョンを確認します。以下のコマンドを実行してください。% nodebrew ls-remoteコマンドを実行すると、次の画像のようにインストールすることができるNode.jsのバージョンが一覧で表示されます。この中から、好きなバージョンを選んでインストールすることができます。
create-react-app
に必要なNode.jsのバージョンは8.1.0以上なので、インストールするバージョンはこれより新しいバージョンであれば問題ありません。では、インストールしていきます。
以下のコマンドを実行します。% nodebrew install stableこのコマンドでは、Node.jsの安定バージョンをインストールすることができます。
stable
が、安定バージョンという意味です。
stable
の部分をlatest
にすると最新バージョンをインストールすることができます。
最新バージョンはバグなどが起きやすいので、基本的には安定バージョンのインストールで大丈夫です。インストールしたNode.jsを確認します。以下のコマンドを実行しましょう。
% nodebrew lsコマンドを実行すると。画像のように現在インストール済みのNode.jsのバージョンが表示されます。ぼくがインストールしているバージョンはv14.4.0です。
初めて使う方「current: none
となっているはずです。以下のコマンドでNode.jsを使えるようにしましょう。バージョンの部分は自分のPCにインストールされているバージョンを入力してください。
% nodebrew use v14.4.0コマンド実行後、もう一度
nodebrew ls
のコマンドを実行すると、current
の部分が変更されているはずです。次にパスを通します。
以下のコマンドを実行してください。(以下のコマンドはshellがzshの場合ですので、気をつけてください)% echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zshrc% source ~/.zshrcここまでやるとNode.jsが使えるようになっているはずです。
以下のコマンドで確認してみましょう。% node -v先ほどcurrentに設定したバージョンが表示されれば、Node.jsのインストールは完了です。
4. npmをインストールする
npmをインストールしていきます。npmとは、Node Package Managerの略で、Node.jsのパッケージを管理するものです。
パッケージとは、Node.jsの便利な機能をまとめたものです。これを簡単に管理できるのがnpmになります。とは言っても、実はNode.jsをインストールした時点でnpmも一緒にインストールされています。
以下のコマンドで確認してみましょう。% npm -vこのように、バージョンが表示されればOKです。
ここまでで、
create-react-app
を使う準備は整いました。
では、create-react-app
していきましょう!5. create-react-appを実行する
まず、
cd
コマンドで自身の開発環境に移動してください。移動したフォルダの直下に開発フォルダが作られます。移動した先で以下のコマンドを実行します。
% npx create-react-app 開発フォルダ名
npx
というコマンドでcreate-react-app
を呼び出しています。npx
を使うことでMacのローカルファイルにcreate-react-app
をインストールすることなく使用できます。
create-react-app
の後に入力したフォルダ名でReactの環境が構築されます。
今回は、フォルダ名をpractice
としました。コマンドを実行して、しばらく待ちます。
npxというコマンドでcreate-react-appを呼び出しています。npxを使うことでMacのローカルファイルにcreate-react-appをインストールすることなく使用できます。
create-react-appの後に入力したフォルダ名でReactの環境が構築されます。
今回は、フォルダ名を「practice」としました。コマンドを実行して、しばらく待ちます。
処理が完了したら環境構築は終了です!
作成したフォルダ内でReactを使う準備ができています。この後は、実際にブラウザに表示させるまでをやってみたいと思います。
エディタで開く
以下のコマンドを実行してエディタで作ったフォルダを開きます。(ぼくはVScodeをエディタとして使っています)
% code practiceエディタで開くとsrcフォルダやpublicフォルダなど色々なフォルダとファイルが自動的に作成されています。
画像はsrcフォルダ内のApp.jsファイルを開いています。
ブラウザに表示する
エディタ内でターミナルを起動し、作業を続けます。
以下のコマンドを実行して、buildフォルダを作成します。コマンドを実行する際は、必ず開発フォルダに移動してから実行してください。
% npm run buildコマンドを実行すると、画像のようにbuildフォルダが作成されます。
このフォルダの中にあるindex.htmlが実際にブラウザに公開するファイルです。
npm run build
ではsrcフォルダとpublicフォルダの中のファイルを一つにまとめてbuildディレクトリに出力しています。buildフォルダが作成できたら、以下のコマンドを実行します。
% npm startこのコマンドを実行するとブラウザが立ち上がり、
create-react-app
で作成した初期の画面が表示されます。
npm start
ではローカルサーバーを起動してReactアプリを確認することができるようになります。Reactでは、srcフォルダ内のApp.jsファイルを書き換えていくことでアプリケーションの中身が変わっていきます。
まとめ
今回はcreate-react-appを使ってReactの環境構築をしました。
1コマンドでReact開発環境を超カンタンに構築できたと思います。Reactの公式ドキュメントによると、
「Create React AppはReactを学習するのに最適な環境であり、Reactで新しいシングルページアプリケーションを作成するのに最も良い方法です」
引用 : React公式ドキュメントと書かれています。
Reactは、現在人気がある言語なのでぜひ勉強してみてください!
最後まで読んでいただいてありがとうございました。ではまた。
- 投稿日:2020-11-15T17:25:15+09:00
Reactでinputの値を更新するたびにフォーカスが外れる
inputをonChangeで更新するとフォーカス外れる
超初歩的なミスなんですが、今まで何度かやってしまっています笑
自分がやってしまうのは、mapでリストをレンダーする際に、onChangeで更新するvalueをkeyの一部にしてしまっているパターンです。
誤った例
List.jsconst List = props => { const [values, setValues] = useState([""]); const handleAdd = () => { //values増やす } const handleChange = (e, i) => { //indexのvalue更新 } return ( <div> //keyにonChangeで更新されるvalueを使っている {values.map((val, i) => <input key={val} onChange={e => handleChange(e, i)}>)} <button onClick={handleAdd}>add value</button> <div> ) }フォーカス外れない例
とりあえず更新する値をkeyにしなければ大丈夫です。indexが変化しないリストであれば、indexをそのまま使っても大丈夫なはず?です。ただ無思考にindex使うのは良くなさそうです。
参考:React: using index as key for items in the list
List.jsconst List = props => { const [values, setValues] = useState([""]); const handleAdd = () => { //values増やす } const handleChange = (e, i) => { //indexのvalue更新 } return ( <div> //keyにindexを使う {values.map((val, i) => <input key={i} onChange={e => handleChange(e, i)}>)} <button onClick={handleAdd}>add value</button> <div> ) }結論
keyがinputで変更されないようにしましょう。
- 投稿日:2020-11-15T16:22:04+09:00
Create-react-appのindex.htmlで条件分岐してmetaタグなどを入れる
Create-react-appで環境毎にhtmlタグを変えたい時のメモ
本番環境と開発環境でmetaタグを変えたかった(開発環境にnoindexを入れたかった)ので、調べてみたらejsのテンプレートに対応してるみたいです。
参考:Conditional content in index.html
ejsの文法
参考:ejs.co
とりあえず<% %>で囲った中はjs書いていいみたいです。変数をそのまま出力するときは<%=何でしょうか。今後使う時が来たら調べてみます。
index.html<% if (user) { %> <h2><%= user.name %></h2> <% } %>結論
自分は下記metaタグ入れたかっただけなので、結論こう書きました。環境変数とかもよく分かってないのでもっとちゃんとしたやり方あるかもしれないです。
ビルド時に本番環境サイトはREACT_APP_ENVにprod、開発環境サイトはREACT_APP_ENVにdevと入れています。
<meta name="robots" content="noindex" />index.html<!DOCTYPE html> <html lang="ja"> <head> <!-- headに入れてるタグ色々 --> <% if(process.env.REACT_APP_ENV === "dev") { %> <meta name="robots" content="noindex" /> <% } %> </head> <body> <div id="root"></div> </body> </html>
- 投稿日:2020-11-15T15:46:38+09:00
React 学習
React
今後のフロントエンドのデファクトスタンダードになるReact
日本国内ではVue.jsの勢いがあるが
今後はReactがくると思っているので自分はReact学びます。誰もが使う技術を使うっていうのは非常に自分としては大事にしています。
- その技術が使える人がいることで保守運用できる。
- 採用する際にも人が集まる。
- 技術の情報が集まる
などなど非常にメリットが多いと思っています。
component志向の言語となるので
画面に必要なパーツを分散させて保守性をあげるという非常にメリットがあるものになります。自分が新規で開発しているサービスで簡単に解説できたらなと思ってます。
App.tsximport React, {createContext, useEffect, useState} from 'react'; import {BrowserRouter as Router, Link, Route, useParams} from 'react-router-dom'; // ComponentのImport import Home from './components/home/Home' import './App.css'; import TimeLine from "./components/home/timeLine"; import Requirement from "./components/requirement/Requirement"; import RequirementDetail from "./components/requirement-detail/RequirementDetail"; const requirementInfo = [ { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' } ] export const RequirementContext = createContext(requirementInfo) function App() { const AppName = 'MeatMeet?' // Hooks const [requirement, setRequirement] = useState(requirementInfo) console.log() useEffect(() => { console.log('hello hooks') }) return ( <> <div className="App"> <RequirementContext.Provider value={requirement}> <Router> <header className="App-header"> <h2>{AppName}</h2> <Link to="/">Home</Link> <Link to="/requirement">募集</Link> </header> <Route exact path="/" component={Home}/> <Route exact path="/requirement" component={Requirement}/> <Route exact path="/requirement/detail/:id" component={RequirementDetail}/> </Router> </RequirementContext.Provider> </div> </> ); } export default App;公正としては
こんな感じになってます。
一番わかりやすいのがhomeだと思うのですが
- Home.tsx
- timeLine.tsx
- timeLineModal.tsx
これに加えて全ての画面で共通して使う場所として
App.tsxもあります。
の三つのコンポーネントが合わさって初めて
Home画面ができるというイメージです。App.tsximport React, {createContext, useEffect, useState} from 'react'; import {BrowserRouter as Router, Link, Route, useParams} from 'react-router-dom'; // ComponentのImport import Home from './components/home/Home' import './App.css'; import TimeLine from "./components/home/timeLine"; import Requirement from "./components/requirement/Requirement"; import RequirementDetail from "./components/requirement-detail/RequirementDetail"; const requirementInfo = [ { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' } ] export const RequirementContext = createContext(requirementInfo) function App() { const AppName = 'MeatMeet?' // Hooks const [requirement, setRequirement] = useState(requirementInfo) console.log() useEffect(() => { console.log('hello hooks') }) return ( <> <div className="App"> <RequirementContext.Provider value={requirement}> <Router> <header className="App-header"> <h2>{AppName}</h2> <Link to="/">Home</Link> <Link to="/requirement">募集</Link> </header> <Route exact path="/" component={Home}/> <Route exact path="/requirement" component={Requirement}/> <Route exact path="/requirement/detail/:id" component={RequirementDetail}/> </Router> </RequirementContext.Provider> </div> </> ); } export default App;Homeコンポーネント
Home.tsximport React, {useEffect, useState, createContext} from 'react'; import Button from '@material-ui/core/Button'; import EventIcon from '@material-ui/icons/Event'; import {BrowserRouter as Router, Link} from "react-router-dom"; import TimeLine from './timeLine' const requirementInfo = [ { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' } ] export const HomeContext = createContext(requirementInfo) function Home() { const [requirement, setRequirement] = useState(requirementInfo) return ( <HomeContext.Provider value={{...requirement, ...requirementInfo}}> <div className="home"> <img src="" alt="homeLogo"/> <Button to="/requirement" component={Link} variant="contained" color="secondary" startIcon={<EventIcon/>} className="requirement">募集する </Button> <TimeLine/> </div> </HomeContext.Provider> ); } export default Home;タイムライン部分
timeLine.tsximport React, {useContext, useEffect} from "react"; import Modal from '@material-ui/core/Modal'; import Button from '@material-ui/core/Button'; import makeStyles from "@material-ui/core/styles/makeStyles"; import {BrowserRouter as Router, Route, Link, Switch, BrowserRouter} from "react-router-dom"; import {withRouter} from 'react-router-dom'; import { useHistory } from 'react-router'; import {HomeContext} from './Home' import Requirement from "../requirement/Requirement"; import RequirementDetail from "../requirement-detail/RequirementDetail"; function TimeLine(this: any) { const requirementInfo = useContext(HomeContext) let info = Object.entries(requirementInfo).map(([key, value]) => ({key, value})) console.log(info) info.map(info => { console.log(info) }) const history = useHistory(); const handleOnclickDetail = (number: number) => (events: any) =>{ history.push('/requirement/detail/'+number,) } return ( <div className="timeLine"> <h3>タイムライン</h3> <Router> {info.map((info,index) => { const resetIndex = index+1 return ( <div className="timeLine__requirement"> <img src="" alt="userLogo"/> <div>{resetIndex}</div> <div className="timeLine__name">イベントNo.{info.value.name}</div> <div className="timeLine__content">{info.value.title}</div> <div className="timeLine__area">{info.value.place}</div> <Button onClick={handleOnclickDetail(resetIndex)}>{resetIndex}へ</Button> </div> ) })} </Router> </div> ) } export default withRouter(TimeLine)タイムラインモーダルコンポーネント
timelineModal.tsximport React from 'react'; import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; import Modal from '@material-ui/core/Modal'; function rand() { return Math.round(Math.random() * 20) - 10; } function getModalStyle() { const top = 50 + rand(); const left = 50 + rand(); return { top: `${top}%`, left: `${left}%`, transform: `translate(-${top}%, -${left}%)`, }; } const useStyles = makeStyles((theme: Theme) => createStyles({ paper: { position: 'absolute', width: 400, backgroundColor: theme.palette.background.paper, border: '2px solid #000', boxShadow: theme.shadows[5], padding: theme.spacing(2, 4, 3), }, }), ); function timeLineModal() { const classes = useStyles(); // getModalStyle is not a pure function, we roll the style only on the first render const [modalStyle] = React.useState(getModalStyle); const [open, setOpen] = React.useState(false); const handleOpen = () => { setOpen(true); }; const handleClose = () => { setOpen(false); }; const body = ( <div style={modalStyle} className={classes.paper}> <h2 id="simple-modal-title">Text in a modal</h2> <p id="simple-modal-description"> Duis mollis, est non commodo luctus, nisi erat porttitor ligula. </p> </div> ); return ( <div> <button type="button" onClick={handleOpen}> Open Modal </button> <Modal open={open} onClose={handleClose} aria-labelledby="simple-modal-title" aria-describedby="simple-modal-description" > {body} </Modal> </div> ); } export default timeLineModalhooks
従来のReactでは状態管理といえばReduxしかなかったため
それが使われていたイメージです。一方で今日ではhooksという技術も使われています。
Reduxに関しては死ぬほどAngular内で学ぶことになりそうなので一旦Hooksの理解に努めます。
Hooksは基本的に親のComponentで定義した値をその
子供のComponentの中でも使えるようにする技術だと自分は理解しています。これを使うことで
全てのComponentで同じ値を用いることができるというのが利点ですよね。それぞれのComponent
で異なる値を用いていた場合、非常に面倒な処理が待っているのが地獄なので
状態管理という手法が今日では用いられています。hooksの中でも
- useState
- useEffect
- useContext
の3つを簡単にまとめられたらと思っています
App.tsximport React, {createContext, useEffect, useState} from 'react'; import {BrowserRouter as Router, Link, Route, useParams} from 'react-router-dom'; // ComponentのImport import Home from './components/home/Home' import './App.css'; import TimeLine from "./components/home/timeLine"; import Requirement from "./components/requirement/Requirement"; import RequirementDetail from "./components/requirement-detail/RequirementDetail"; const requirementInfo = [ { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' } ] export const RequirementContext = createContext(requirementInfo) function App() { const AppName = 'MeatMeet?' // Hooks const [requirement, setRequirement] = useState(requirementInfo) console.log() useEffect(() => { console.log('hello hooks') }) return ( <> <div className="App"> <RequirementContext.Provider value={requirement}> <Router> <header className="App-header"> <h2>{AppName}</h2> <Link to="/">Home</Link> <Link to="/requirement">募集</Link> </header> <Route exact path="/" component={Home}/> <Route exact path="/requirement" component={Requirement}/> <Route exact path="/requirement/detail/:id" component={RequirementDetail}/> </Router> </RequirementContext.Provider> </div> </> ); } export default App;useStateを使う
const [count, setCount] = useState(0)第一引数にパラメータが入ってきます。
第二引数は第一引数にあるパラメータを変化させる時に用います。また右辺のuseStateの引数には初期値requirementに入れたい初期値が入ってきます。
この場合はcount=0が入ってきます。
<button onClick={():void => setCount(count => count+1)}>+1</button>そして上記のように書いてあげることでボタンをおすびにcountが+1されるような処理が行われます。
(void属性で帰ってくるように指定してあげないとtsxではエラーが表示されます。。。)
これで少しTypeScriptについて詳しくなった気がします。useEffectを使う
useEffectはレンダーの結果が画面に反映された後に表示されます。
useEffect(() => { //レンダーの結果が画面に表示された後表示される console.log('effect'); }import React, {createContext, useEffect, useState} from 'react'; import {BrowserRouter as Router, Link, Route, useParams} from 'react-router-dom'; // ComponentのImport import Home from './components/home/Home' import './App.css'; import TimeLine from "./components/home/timeLine"; import Requirement from "./components/requirement/Requirement"; import RequirementDetail from "./components/requirement-detail/RequirementDetail"; const requirementInfo = [ { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' }, { 'name': '石田', 'title': '新宿で焼肉会', 'place': '新宿' } ] export const RequirementContext = createContext(requirementInfo) function App() { const AppName = 'MeatMeet?' // Hooks const [requirement, setRequirement] = useState(requirementInfo) const [count, setCount] = useState(0) console.log('render start') useEffect(() => { console.log('render finished') }) }) ...というファイルの場合
render start
render finishedの順に描画されていることがわかるかと思います。
APIで画面描画後に何かしら処理したい
時などに用いられます。また第二引数にcontextを指定して指定したparamが変わった場合にだけ
effectを実行するという指定もできます。全ての変更で全く違う処理が走ると大変ですからね。
createContextを使う
createContext
export const RequirementContext = createContext(requirementInfo)createContextで定義することによってcontextが生成されます。
contextとは言うなればglobalに使えるstateという認識になります。
親で定義した共通の値を子コンポーネントで用いるためにまず準備してあげます。
useContextを使う
RequirementDetail.tsximport React, {useEffect, useState, createContext, useContext} from 'react'; import Button from '@material-ui/core/Button'; import EventIcon from '@material-ui/icons/Event'; import TextField from '@material-ui/core/TextField'; import { Route, Switch, useParams, useHistory, useLocation, } from 'react-router-dom'; import {RequirementContext} from "../../App"; function RequirementDetail(this: any) { const urlParam:any = useParams(); const requirementId = urlParam.id const requirements = useContext(RequirementContext) const requirementDetailInfo = requirements[requirementId-1] return ( <> <div className="requirementDetail"> <div className="requirementDetail__timeline"> TimeLine </div> <div> <div className="requirementDetail__detail"> 募集詳細: ID: {requirementId} タイトル: {requirementDetailInfo.title} 募集者: {requirementDetailInfo.name} 場所: {requirementDetailInfo.place} </div> <div className="requirementDetail__talk"> やりとり </div> </div> </div> </> ); } export default RequirementDetail;const requirements = useContext(RequirementContext)これが肝になります。
親componentからimportしてきてた RequirementContext
をここで用いるというのを宣言してあげています。またobjectのまま画面に表示させ量とすると
Objects are not valid as a React child (found: object with keys {name, title, place}). If you meant to render a collection of children, use an array instead.というobjectはreactでは表示できないというエラーが発生するので展開してあげる必要があります。
今回は詳細画面なので必要なものだけ処理していますが
リストのように一覧表示させたいときはmapやスプレッド構文などで対応してあげる必要があります。簡単にまとめてみましたが
まだまだ自分も使いこなせるわけではないのでこれからも精進していきます。
- 投稿日:2020-11-15T12:54:06+09:00
Twitter APIを使用してツイートしたいけど"Your credentials do not allow access to this resource."が出てくる時の対処法
前提/環境
- Twitter(Twitter for Node.js)というライブラリを使用しています
- FirebaseのCloudfunctionsを使用してその中で上記のTwitterを使用してロジックを作っています
- Twitter Developersでの各種キーの取得(Consumer API keys, Access token & access token secret)は全て終わっています
やりたいこと、できなかったこと
- 自分が作ったWebアプリケーションの中で、Twitterでログインしたユーザが各自のTwitterアカウントでツイートできるようにすること
原因
- 原因は、以下のようにbearer_tokenをセットしてしまっていたことでした
functions/index.jsconst client = new Twitter({ consumer_key: vz***** /** consumer key */, consumer_secret: mT***** /** consumer secret */, bearer_token: ******* /** Bearer Token */, access_token_key: 11*****, access_token_secret: 0d******* });Bearer Tokenを使用してユーザーコンテキスト(statuss / home_timelineなど)を必要とするエンドポイントをリクエストしてはいけないみたいです。
以下のように修正して無事にTwitter投稿ができました。
functions/index.jsconst client = new Twitter({ consumer_key: vz***** /** consumer key */, consumer_secret: mT***** /** consumer secret */, access_token_key: 11*****, access_token_secret: 0d***** });(TwitterAPIをNodeで何かしたい時は、Twitterライブラリ使うのがおすすめです。)
以上です。何かのお役にたてば幸いです!
- 投稿日:2020-11-15T12:00:44+09:00
Reactでnpm ERR! code E404が出た時の対処方法
- 投稿日:2020-11-15T08:28:57+09:00
Recoilの概要
この記事はCore Concepts の翻訳記事です。 (2020/11/15時点)
Overview
Recoil を使うと、atoms(共有ステート) から selectors(純粋関数) を通してReactコンポーネントに流れていくデータフローを構築できます。
Atoms は コンポーネントがsubscribeするステートの単位で、selectors は同期的または非同期的にステートを変形します。
Atoms
atomsはステートを表す単位となるものです。
atomsは更新可能でsubscribe可能な値です。つまりatomは文字通り更新できて、それに依存しているコンポーネントは更新時に新しいatomの値で再レンダリングされます。
また、atomsはランタイムによって構築されます。atomsはReactコンポーネントのローカルなステートと同じ場所で使うことができ、もし同じatomが複数のコンポーネントで利用されているとき、コンポーネントのatomは同じステートを共有します。
atomsは
atom
関数を使って作成します。const fontSizeState = atom({ key: 'fontSizeState', default: 14, });atomsの作成には一意なkeyを設定することが必要です。これはデバッグ、一貫性の維持、全てのatomsを俯瞰するためのadvanced APIなどで使われます。
もしatomsでkeyが重複する場合はエラーを起こしてしまうため、必ずこれは一意な値を使う必要があります。
またReactコンポーネントのステートと同様にデフォルト値を持ちます。
コンポーネントからatomを読み書きするためには、
useRecoilState
と呼ばれるフックAPIを用います。Reactの
useState
と見た目は似ていますが、ステートはatomsを使うコンポーネント間で共有されている点が異なっています。function FontButton() { const [fontSize, setFontSize] = useRecoilState(fontSizeState); return ( <button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}> Click to Enlarge </button> ); }この例では、ボタンをクリックするとフォントサイズがインクリメントされます。しかもこれは、他のコンポーネントのフォントサイズにも波及します。
Selectors
selectorはatomsまたは他のselectorsを入力として受け取る純粋関数です。
上流つまり入力として渡したatomsやselectorsが更新されると、それを受け取るselector関数も再評価(更新)されます。
selectorsをsubscribeしているコンポーネントは、atomsの
useRecoilState
のように、selectorが更新されたときに再レンダリングされます。selectors は ステートに基づいて派生データを計算するために使用されます。
selectorを使うことでreducerがステートを同期して有効に保つ必要がなくなり、余計なステートが増えることが回避できます。
その代わり、atomsには最低限必要なステートのみが格納され、他のステートはそこから派生する関数、つまりはselectorによって効率的に計算されます。
selectorはコンポーネントが何に依存しているかをトラックしてくれるおかげで関数的なアプローチがより効率的に取れるようになっています。
コンポーネントから見ると、selectorもatomも全く同じインターフェースをしているため、selectorとatomはお互いに置き換え可能になっています。
selectorsは
selector
関数を使って定義されます。const fontSizeLabelState = selector({ key: 'fontSizeLabelState', get: ({get}) => { const fontSize = get(fontSizeState); const unit = 'px'; return `${fontSize}${unit}`; }, });
get
プロパティは派生するステートを計算する関数です。この関数はatomsや他のselectorsの値をget()
の引数を通して受け取ることができます。atomsやselectorsにアクセスすると依存関係が構築され、依存元が更新されたときに再計算が行われるようになっています。今回の
fontSizeLabelState
ではselectorはfontSizeState
atomにだけ依存しています。コンセプト通り、
fontSizeLabelState
selectorはfontSizeState
を入力として受け取って、フォーマットされたフォントサイズのラベルを出力する純粋関数のように振舞います。selectorは
useRecoilValue()
を使って読み取り可能です。これは引数にatomかselectorを受け取り、それに対応する値を返すAPIです。
fontSizeLabelState
selectorは書き込み不可能なので、useRecoilState()
は使用しません。 (書き込み可能なselectorについて知りたい場合はselector API reference参照)function FontButton() { const [fontSize, setFontSize] = useRecoilState(fontSizeState); const fontSizeLabel = useRecoilValue(fontSizeLabelState); return ( <> <div>Current font size: ${fontSizeLabel}</div> <button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}> Click to Enlarge </button> </> ); }ボタンをクリックすると、ボタンのフォントサイズが増加し、現在のフォントサイズをフォーマットして表している
${fontSizeLabel}
にもその変更が反映されます。
- 投稿日:2020-11-15T03:30:22+09:00
【React Hooks入門】useStateを使って買い物リストを作るチュートリアル
はじめに
これはReact Hooksの入門者向けの記事で、今回はuseStateを扱っています。
前半部分でReact HooksやuseStateの解説を行い、後半部分で関数コンポーネントを使った買い物リストの実装をチュートリアル形式で行っています。チュートリアルではfreeCodeCampに掲載されていたHow to Build a Shopping List Using React Hooks (w/ Starter Code and Video Walkthrough)を実例として進めていきながら、解説を織り交ぜています。
React学習の参考になれば幸いです。
このチュートリアルで学べる事
React Hooksの基礎にあたるuseStateの使い方の基礎。
関数コンポーネントでのstateの扱い方の基礎チュートリアルの完成像
解説編
React Hooksとは
React Hooksとは2019年2月からReactに追加された機能です。
これを使う事で、クラスを使わずに関数コンポーネントだけでもstateを扱うことが出来る様になりました。関数コンポーネントで書くことで、クラスコンポーネントより処理がまとめやすくスッキリしたフォルダ構成に出来るなど、様々な恩恵が受けられます。
その他も様々な違いがあります。詳しくはこちらの記事などを参考にしてください。
https://tyotto-good.com/blog/reaseons-to-use-function-componentuseStateとは
useStateを使って書くことで、React Hooksでは状態の定義や更新ができます。
ボタンを押すとcountが+1されるカウンターを例に見ていきましょう。const Counter = () => { const [count, setCount] = useState(0); return <div onClick={() => setCount(count+1)}>{count}</div> };
const [count, setCount] = useState(0);
では
左辺のcountが変数の定義で、setCountがcountの状態を更新する関数です。
右辺のuseState()では、()の中にcountの初期値を入れることができます。今回は0を初期値として入れています。
onClick={() => setCount(count+1)}
では
クリック時に更新関数のsetCountが実行されて、countに+1しています。useStateはこの様に
const [変数, 変数の状態を更新する関数] = useState(初期値);
で定義を行い、変数の状態を更新する関数を実行することで、状態を変えていきます。チュートリアル編
useStateの基本的な使い方を解説したところで、実際に買い物リストのチュートリアルに入りましょう。
チュートリアルの手順
- スタートの状態を作る
- 新しいアイテムを追加する
- アイテムの完了/未完了を切り替える
- アイテムの個数を増減できる様にする
- 総量を表示する
1. スタートの状態を作る
まずは手物で環境を作りましょう。
僕のgithubアカウントに初期状態を作っておきましたので、git clone
して手元に持ってきてください。
https://github.com/gunners6518/shopping-liststarterフォルダ配下のApp.jsに実際のコードを書いていきます。
git clone
ができたら、yarn install
、yarn start
を行いブラウザで正しく表示されているかを確認しましょう。2. 新しいアイテムを追加する
Add an itemに値を入力して、+ボタンを押すことで、買い物リストにアイテムが新規追加される様にします。
大まかな流れとしては以下の通りです。
- 買い物リストを表示する
- 入力値が変わるたびに、その値を保存する
- +ボタンを押した際に、アイテムを買い物リストに新規追加する
では進めていきましょう。
買い物リストを表示する
まずは買い物リストを表示できる様にしましょう。
といっても初期状態では、買い物リストは0個なので、表示自体は出来ないですが、、買い物リストの一覧表示と聞くと、多くの人がfor文を想像するかもしれませんが。
Reactではmapを使います。(Reactではというより、正確には関数コンポーネントでは)mapの使い方について不安な方はこちらの記事が参考になります。
https://qiita.com/ryosuketter/items/c11a58242fa1ad9c6e4creturnのDOMの中にitem-listの部分を書き足してください。
<div className='item-list'> {items.map((item, index) => ( <div className='item-container'> <div className='item-name'> {item.isSelected ? ( <> <FontAwesomeIcon icon={faCheckCircle} /> <span className='completed'>{item.itemName}</span> </> ) : ( <> <FontAwesomeIcon icon={faCircle} /> <span>{item.itemName}</span> </> )} </div> <div className='quantity'> <button> <FontAwesomeIcon icon={faChevronLeft} /> </button> <span> {item.quantity} </span> <button> <FontAwesomeIcon icon={faChevronRight} /> </button> </div> </div> ))} </div>まず最初に、13行目でuseStateを使って、買い物リスト一覧のオブジェクトであるitemsを定義している部分を確認しましょう。
初期値は([ ])とする事で、からのオブジェクトが定義されており、初期状態では買い物リストは空になります。const [items, setItems] = useState([]);次に、先ほど書いたreturn内のコードを解説していきます。
{items.map((item, index) => (~~~))}itemsをmapにて加工して、
{item.itemName}
や{item.quantity}
など、items内のデータを含むDOMで返しています。
これによってitemsの要素1つ1つを買い物リストとして表示できます。また、mapの引数にあったindexは、あとで特定の買い物リストに対して個数の増減や、todo⇄doneの切り替えなどに使う予定です。
入力値が変わるたびに、その値を保存する
入力値の状態が変わるたびに、その変更を受け取り保存したい為、useStateを使います。
まず入力値のuseStateを定義しましょう。const [inputValue, setInputValue] = useState('');初期値は空ですね。
次に19行目(付近)のinput内にチェンジイベント仕込みましょう。<input value={inputValue} onChange={(event) => setInputValue(event.target.value)} className='add-item-input' placeholder='Add an item...' />onChangeを使う事で、入力値が変わるたびにイベントが発火されます。
useStateの更新関数であるsetInputValue
の引数にevent.target.value
を入れる事で、入力値をinputValueに反映されます。useStateを使う事で、input内の値が変わるたびに、inputValueの値が更新される状態を作れました。
+ボタンを押した際に、アイテムを買い物リストに新規追加する
次にuseStateを使い、itemsに新しいオブジェクトを追加していきます。
まずは+ボタンがクリックされた時に走る処理を書きましょう。//クリック時にitems配列に新しいitemを作る処理 const handleAddButtonClick = () => { //作られるitemの定義 const newItem = { itemName: inputValue, quantity: 1, isSelected: false, }; //items配列にpushされる const newItems = [...items, newItem]; //useStateのitemsに反映 setItems(newItems); //入力値を空に setInputValue(""); };まずは
const newItem{}
にて新しく作られる買い物リストのオブジェクトの初期値を定義しています。
itemNameはアイテムの名前、quantityはアイテムの個数、isSelectedはtodoかdoneかを値として持っています。
itemName: inputValue,
では、先ほど常に入力値が反映される様になったinputValue
を新規買い物リストの名前にの部分に入れています。const newItems = [...items, newItem];ではスプレット構文を使って、itemsのオブジェクトの最後にnewItemを入れたnewItemsという配列を作っています。買い物リストを新規追加した後は更新関数setItem()を使って、反映させています。
クリック時に処理はできたので、+ボタンをクリックした際に処理が走る様にします。
<FontAwesomeIcon icon={faPlus} onClick={() => handleAddButtonClick()} />これにて、入力値を入れて、+ボタンを押せば、買い物リストが新規追加されます。
3. アイテムの完了/未完了を切り替える
さきほどのmapの配列時に書いてあったindexを引数に用いて、特定のタスクの状態を切り替えます。
この様な処理を書いてください。//done切り替え const toggleComplete = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //引数にindexから、該当するitemのisSelectedを切り替える newItems[index].isSelected = !newItems[index].isSelected; setItems(newItems); };
const newItems = [...items];
を行う事で、itemsオブジェクトを展開してnewItemsという配列にしています。
配列にしたのでindexを用いて特定のアイテムのisSelected
まで到達しています。newItems[index].isSelected = !newItems[index].isSelected;処理としてはisSelectedのboolean型を切り替えていますね。
toggleCompleteの処理ができたので、今まで同様にクリックした際に発火させます。
<div className="item-name" onClick={() => toggleComplete(index)}>これにてtodo⇄doneの切り替えの機能は完成です。
4. アイテムの個数を増減できる様にする
実はやり方はほとんど、3の完了未完了の切り替えと同じです。
クリック時に起こる処理をuseStateの更新を使って書き、ボタンをクリックした際にそれが発火する様にします。一気にコードを加えていきましょう。
まずは増える際の処理。
itemsオブジェクトを展開して、indexから特定のアイテムの数量を+1しています。const handleQuantityIncrease = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //quantityに+1する newItems[index].quantity++; setItems(newItems); };次に減る際の処理。ほとんど一緒ですね。
const handleQuantityDecrease = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //quantityに-1する newItems[index].quantity--; setItems(newItems); };増えるイベントと、減るイベントをボタンから発火させます。
<button> <FontAwesomeIcon icon={faChevronRight} onClick={() => handleQuantityIncrease(index)} /> </button><button> <FontAwesomeIcon icon={faChevronLeft} onClick={() => handleQuantityIDecrease(index)} /> </button>これにて増減の機能は完成です。
5. 総量を表示する
総量の表示もuseStateを使って、アイテムの数が増減する、アイテムが新規で作られるといった時に、数が更新される様にします。
まずはuseStateを使って、定義を作ります。
const [totalItemCount, setTotalItemCount] = useState(0);トータル表示のところでは、useStateにて定義した
totalItemCount
を入れていきます。<div className='total'>Total: {totalItemCount}</div>次に、最新の総量を計算する処理を書きます。
const calculateTotal = () => { const totalItemCount = items.reduce((total, item) => { return total + item.quantity; }, 0); setTotalItemCount(totalItemCount); };
items.reduce((total, item) => {return total + item.quantity;}, 0);
ではreduceを使う事で、itemsオブジェクトからitem.quantityを1つずつ抜き取り、totalにその値を加えるということを繰り返すことで、item.quantity
の合計値を出しています。最後に
calculateTotal
を個数が変わる処理の最後に入れて、再計算を毎回行える様にしてください。。
こんな感じになります。const handleAddButtonClick = () => { // ...other code calculateTotal(); }; const handleQuantityIncrease = (index) => { // ...other code calculateTotal(); }; const handleQuantityDecrease = (index) => { // ...other code calculateTotal(); };ついに完成!!
お疲れ様です。
皆さんの手元でも、この様にちゃんと動作していますか??上手くいかなくてどうしても原因がわからない時は、github内にfinalというフォルダがあり、完成形のコードが入っているので参考にしてみてください。
まとめ
このチュートリアルではuseStateで、items、inputValue、totalItemCounと3つ状態管理をおこなっていきました。
実際に手を動かしuseStateを扱うことで、関数コンポーネントで状態を扱っていくことのイメージは掴めたと思います。参考になったらLGTMしてもらえると嬉しいです。
Twitterやブログもやっているので、興味あればご覧になってください!ではまた。。
- 投稿日:2020-11-15T03:30:22+09:00
【 React Hooks入門】useStateを使ってシンプルな買い物リストを作るチュートリアル
はじめに
これはReact Hooksの入門者向けの記事で、今回はuseStateを扱っています。
前半部分でReact HooksやuseStateの解説を行い、後半部分で関数コンポーネントを使った買い物リストの実装をチュートリアル形式で行っています。チュートリアルではfreeCodeCampに掲載されていた、How to Build a Shopping List Using React Hooks (w/ Starter Code and Video Walkthrough)を実例として進めていきながら、解説を織り交ぜています。
React学習の参考になれば幸いです。
このチュートリアルで学べる事
React Hooksの基礎にあたるuseStateの使い方の基礎。
関数コンポーネントでのstateの扱い方の基礎チュートリアルの完成像
解説編
React Hooksとは
React Hooksとは2019年2月からReactに追加された機能です。
これを使う事で、クラスを使わずに関数コンポーネントだけでもstateを扱うことが出来る様になりました。関数コンポーネントで書くことで、クラスコンポーネントより処理がまとめやすくスッキリしたフォルダ構成に出来るなど、様々な恩恵が受けられます。
その他も様々な違いがあります。詳しくはこちらの記事などを参考にしてください。
https://tyotto-good.com/blog/reaseons-to-use-function-componentuseStateとは
useStateを使って書くことで、React Hooksでは状態の定義や更新ができます。
ボタンを押すとcountが+1されるカウンターを例に見ていきましょう。const Counter = () => { const [count, setCount] = useState(0); return <div onClick={() => setCount(count+1)}>{count}</div> };
const [count, setCount] = useState(0);
では
左辺のcountが変数の定義で、setCountがcountの状態を更新する関数です。
右辺のuseState()では、()の中にcountの初期値を入れることができます。今回は0を初期値として入れています。
onClick={() => setCount(count+1)}
では
クリック時に更新関数のsetCountが実行されて、countに+1しています。useStateはこの様に
const [変数, 変数の状態を更新する関数] = useState(初期値);
で定義を行い、変数の状態を更新する関数を実行することで、状態を変えていきます。チュートリアル編
useStateの基本的な使い方を解説したところで、実際に買い物リストのチュートリアルに入りましょう。
チュートリアルの手順
- スタートの状態を作る
- 新しいアイテムを追加する
- アイテムの完了/未完了を切り替える
- アイテムの個数を増減できる様にする
- 総量を表示する
1. スタートの状態を作る
まずは手物で環境を作りましょう。
僕のgithubアカウントに初期状態を作っておきましたので、git clone
して手元に持ってきてください。
https://github.com/gunners6518/shopping-liststarterフォルダ配下のApp.jsに実際のコードを書いていきます。
git clone
ができたら、yarn install
、yarn start
を行いブラウザで正しく表示されているかを確認しましょう。2. 新しいアイテムを追加する
Add an itemに値を入力して、+ボタンを押すことで、買い物リストにアイテムが新規追加される様にします。
大まかな流れとしては以下の通りです。
- 買い物リストを表示する
- 入力値が変わるたびに、その値を保存する
- +ボタンを押した際に、アイテムを買い物リストに新規追加する
では進めていきましょう。
買い物リストを表示する
まずは買い物リストを表示できる様にしましょう。
といっても初期状態では、買い物リストは0個なので、表示自体は出来ないですが、、買い物リストの一覧表示と聞くと、多くの人がfor文を想像するかもしれませんが。
Reactではmapを使います。(Reactではというより、正確には関数コンポーネントでは)mapの使い方について不安な方はこちらの記事が参考になります。
https://qiita.com/ryosuketter/items/c11a58242fa1ad9c6e4creturnのDOMの中にitem-listの部分を書き足してください。
<div className='item-list'> {items.map((item, index) => ( <div className='item-container'> <div className='item-name'> {item.isSelected ? ( <> <FontAwesomeIcon icon={faCheckCircle} /> <span className='completed'>{item.itemName}</span> </> ) : ( <> <FontAwesomeIcon icon={faCircle} /> <span>{item.itemName}</span> </> )} </div> <div className='quantity'> <button> <FontAwesomeIcon icon={faChevronLeft} /> </button> <span> {item.quantity} </span> <button> <FontAwesomeIcon icon={faChevronRight} /> </button> </div> </div> ))} </div>まず最初に、13行目でuseSta手を使って、買い物リスト一覧のオブジェクトであるitemsを定義している部分を確認しましょう。
初期値は([ ])とする事で、からのオブジェクトが定義されており、初期状態では買い物リストは空になります。const [items, setItems] = useState([]);次に、先ほど書いたreturn内のコードを解説していきます。
{items.map((item, index) => (~~~))}itemsをmapにて加工して、
{item.itemName}
や{item.quantity}
など、items内のデータを含むDOMで返しています。
これによってitemsの要素1つ1つを買い物リストとして表示できます。また、mapの引数にあったindexは、あとで特定の買い物リストに対して個数の増減や、todo⇄doneの切り替えなどに使う予定です。
入力値が変わるたびに、その値を保存する
入力値の状態が変わるたびに、その変更を受け取り保存したい為、useStateを使います。
まず入力値のuseStateを定義しましょう。const [inputValue, setInputValue] = useState('');初期値は空ですね。
次に19行目(付近)のinput内にチェンジイベント仕込みましょう。<input value={inputValue} onChange={(event) => setInputValue(event.target.value)} className='add-item-input' placeholder='Add an item...' />onChangeを使う事で、入力値が変わるたびにイベントが発火されます。
useStateの更新関数であるsetInputValue
の引数にevent.target.value
を入れる事で、入力値をinputValueに反映されます。useStateを使う事で、input内の値が変わるたびに、inputValueの値が更新される状態を作れました。
+ボタンを押した際に、アイテムを買い物リストに新規追加する
次にuseStateを使い、itemsに新しいオブジェクトを追加していきます。
まずは+ボタンがクリックされた時に走る処理を書きましょう。//クリック時にitems配列に新しいitemを作る処理 const handleAddButtonClick = () => { //作られるitemの定義 const newItem = { itemName: inputValue, quantity: 1, isSelected: false, }; //items配列にpushされる const newItems = [...items, newItem]; //useStateのitemsに反映 setItems(newItems); //入力値を空に setInputValue(""); };まずは
const newItem{}
にて新しく作られる買い物リストのオブジェクトの初期値を定義しています。
itemNameはアイテムの名前、quantityはアイテムの個数、isSelectedはtodoかdoneかを値として持っています。
itemName: inputValue,
では、先ほど常に入力値が反映される様になったinputValue
を新規買い物リストの名前にの部分に入れています。const newItems = [...items, newItem];ではスプレット構文を使って、itemsのオブジェクトの最後にnewItemを入れたnewItemsという配列を作っています。買い物リストを新規追加した後は更新関数setItem()を使って、反映させています。
クリック時に処理はできたので、+ボタンをクリックした際に処理が走る様にします。
<FontAwesomeIcon icon={faPlus} onClick={() => handleAddButtonClick()} />これにて、入力値を入れて、+ボタンを押せば、買い物リストが新規追加されます。
3. アイテムの完了/未完了を切り替える
さきほどのmapの配列時に書いてあったindexを引数に用いて、特定のタスクの状態を切り替えます。
この様な処理を書いてください。//done切り替え const toggleComplete = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //引数にindexから、該当するitemのisSelectedを切り替える newItems[index].isSelected = !newItems[index].isSelected; setItems(newItems); };
const newItems = [...items];
を行う事で、itemsオブジェクトを展開してnewItemsという配列にしています。
配列にしたのでindexを用いて特定のアイテムのisSelected
まで到達しています。newItems[index].isSelected = !newItems[index].isSelected;処理としてはisSelectedのboolean型を切り替えていますね。
toggleCompleteの処理ができたので、今まで同様にクリックした際に発火させます。
<div className="item-name" onClick={() => toggleComplete(index)}>これにてtodo⇄doneの切り替えの機能は完成です。
4. アイテムの個数を増減できる様にする
実はやり方はほとんど、3の完了未完了の切り替えと同じです。
クリック時に起こる処理をuseStateの更新を使って書き、ボタンをクリックした際にそれが発火する様にします。一気にコードを加えていきましょう。
まずは増える際の処理。
itemsオブジェクトを展開して、indexから特定のアイテムの数量を+1しています。const handleQuantityIncrease = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //quantityに+1する newItems[index].quantity++; setItems(newItems); };次に減る際の処理。ほとんど一緒ですね。
const handleQuantityDecrease = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //quantityに-1する newItems[index].quantity--; setItems(newItems); };増えるイベントと、減るイベントをボタンから発火させます。
<button> <FontAwesomeIcon icon={faChevronRight} onClick={() => handleQuantityIncrease(index)} /> </button><button> <FontAwesomeIcon icon={faChevronLeft} onClick={() => handleQuantityIDecrease(index)} /> </button>これにて増減の機能は完成です。
5. 総量を表示する
総量の表示もuseStateを使って、アイテムの数が増減する、アイテムが新規で作られるといった時に、数が更新される様にします。
まずはuseStateを使って、定義を作ります。
const [totalItemCount, setTotalItemCount] = useState(0);トータル表示のところでは、useStateにて定義した
totalItemCount
を入れていきます。<div className='total'>Total: {totalItemCount}</div>次に、最新の総量を計算する処理を書きます。
const calculateTotal = () => { const totalItemCount = items.reduce((total, item) => { return total + item.quantity; }, 0); setTotalItemCount(totalItemCount); };
items.reduce((total, item) => {return total + item.quantity;}, 0);
ではreduceを使う事で、itemsオブジェクトからitem.quantityを1つずつ抜き取り、totalにその値を加えるということを繰り返すことで、item.quantity
の合計値を出しています。最後に
calculateTotal
を個数が変わる処理の最後に入れて、再計算を毎回行える様にしてください。。
こんな感じになります。const handleAddButtonClick = () => { // ...other code calculateTotal(); }; const handleQuantityIncrease = (index) => { // ...other code calculateTotal(); }; const handleQuantityDecrease = (index) => { // ...other code calculateTotal(); };ついに完成!!
お疲れ様です。
皆さんの手元でも、この様にちゃんと動作していますか??上手くいかなくてどうしても原因がわからない時は、github内にfinalというフォルダがあり、完成形のコードが入っているので参考にしてみてください。
まとめ
このチュートリアルではuseStateで、items、inputValue、totalItemCounと3つ状態管理をおこなっていきました。
実際に手を動かしuseStateを扱うことで、関数コンポーネントで状態を扱っていくことのイメージは掴めたと思います。ではまた。。
- 投稿日:2020-11-15T03:30:22+09:00
【 React Hooks入門】useStateを使って買い物リストを作るチュートリアル
はじめに
これはReact Hooksの入門者向けの記事で、今回はuseStateを扱っています。
前半部分でReact HooksやuseStateの解説を行い、後半部分で関数コンポーネントを使った買い物リストの実装をチュートリアル形式で行っています。チュートリアルではfreeCodeCampに掲載されていたHow to Build a Shopping List Using React Hooks (w/ Starter Code and Video Walkthrough)を実例として進めていきながら、解説を織り交ぜています。
React学習の参考になれば幸いです。
このチュートリアルで学べる事
React Hooksの基礎にあたるuseStateの使い方の基礎。
関数コンポーネントでのstateの扱い方の基礎チュートリアルの完成像
解説編
React Hooksとは
React Hooksとは2019年2月からReactに追加された機能です。
これを使う事で、クラスを使わずに関数コンポーネントだけでもstateを扱うことが出来る様になりました。関数コンポーネントで書くことで、クラスコンポーネントより処理がまとめやすくスッキリしたフォルダ構成に出来るなど、様々な恩恵が受けられます。
その他も様々な違いがあります。詳しくはこちらの記事などを参考にしてください。
https://tyotto-good.com/blog/reaseons-to-use-function-componentuseStateとは
useStateを使って書くことで、React Hooksでは状態の定義や更新ができます。
ボタンを押すとcountが+1されるカウンターを例に見ていきましょう。const Counter = () => { const [count, setCount] = useState(0); return <div onClick={() => setCount(count+1)}>{count}</div> };
const [count, setCount] = useState(0);
では
左辺のcountが変数の定義で、setCountがcountの状態を更新する関数です。
右辺のuseState()では、()の中にcountの初期値を入れることができます。今回は0を初期値として入れています。
onClick={() => setCount(count+1)}
では
クリック時に更新関数のsetCountが実行されて、countに+1しています。useStateはこの様に
const [変数, 変数の状態を更新する関数] = useState(初期値);
で定義を行い、変数の状態を更新する関数を実行することで、状態を変えていきます。チュートリアル編
useStateの基本的な使い方を解説したところで、実際に買い物リストのチュートリアルに入りましょう。
チュートリアルの手順
- スタートの状態を作る
- 新しいアイテムを追加する
- アイテムの完了/未完了を切り替える
- アイテムの個数を増減できる様にする
- 総量を表示する
1. スタートの状態を作る
まずは手物で環境を作りましょう。
僕のgithubアカウントに初期状態を作っておきましたので、git clone
して手元に持ってきてください。
https://github.com/gunners6518/shopping-liststarterフォルダ配下のApp.jsに実際のコードを書いていきます。
git clone
ができたら、yarn install
、yarn start
を行いブラウザで正しく表示されているかを確認しましょう。2. 新しいアイテムを追加する
Add an itemに値を入力して、+ボタンを押すことで、買い物リストにアイテムが新規追加される様にします。
大まかな流れとしては以下の通りです。
- 買い物リストを表示する
- 入力値が変わるたびに、その値を保存する
- +ボタンを押した際に、アイテムを買い物リストに新規追加する
では進めていきましょう。
買い物リストを表示する
まずは買い物リストを表示できる様にしましょう。
といっても初期状態では、買い物リストは0個なので、表示自体は出来ないですが、、買い物リストの一覧表示と聞くと、多くの人がfor文を想像するかもしれませんが。
Reactではmapを使います。(Reactではというより、正確には関数コンポーネントでは)mapの使い方について不安な方はこちらの記事が参考になります。
https://qiita.com/ryosuketter/items/c11a58242fa1ad9c6e4creturnのDOMの中にitem-listの部分を書き足してください。
<div className='item-list'> {items.map((item, index) => ( <div className='item-container'> <div className='item-name'> {item.isSelected ? ( <> <FontAwesomeIcon icon={faCheckCircle} /> <span className='completed'>{item.itemName}</span> </> ) : ( <> <FontAwesomeIcon icon={faCircle} /> <span>{item.itemName}</span> </> )} </div> <div className='quantity'> <button> <FontAwesomeIcon icon={faChevronLeft} /> </button> <span> {item.quantity} </span> <button> <FontAwesomeIcon icon={faChevronRight} /> </button> </div> </div> ))} </div>まず最初に、13行目でuseSta手を使って、買い物リスト一覧のオブジェクトであるitemsを定義している部分を確認しましょう。
初期値は([ ])とする事で、からのオブジェクトが定義されており、初期状態では買い物リストは空になります。const [items, setItems] = useState([]);次に、先ほど書いたreturn内のコードを解説していきます。
{items.map((item, index) => (~~~))}itemsをmapにて加工して、
{item.itemName}
や{item.quantity}
など、items内のデータを含むDOMで返しています。
これによってitemsの要素1つ1つを買い物リストとして表示できます。また、mapの引数にあったindexは、あとで特定の買い物リストに対して個数の増減や、todo⇄doneの切り替えなどに使う予定です。
入力値が変わるたびに、その値を保存する
入力値の状態が変わるたびに、その変更を受け取り保存したい為、useStateを使います。
まず入力値のuseStateを定義しましょう。const [inputValue, setInputValue] = useState('');初期値は空ですね。
次に19行目(付近)のinput内にチェンジイベント仕込みましょう。<input value={inputValue} onChange={(event) => setInputValue(event.target.value)} className='add-item-input' placeholder='Add an item...' />onChangeを使う事で、入力値が変わるたびにイベントが発火されます。
useStateの更新関数であるsetInputValue
の引数にevent.target.value
を入れる事で、入力値をinputValueに反映されます。useStateを使う事で、input内の値が変わるたびに、inputValueの値が更新される状態を作れました。
+ボタンを押した際に、アイテムを買い物リストに新規追加する
次にuseStateを使い、itemsに新しいオブジェクトを追加していきます。
まずは+ボタンがクリックされた時に走る処理を書きましょう。//クリック時にitems配列に新しいitemを作る処理 const handleAddButtonClick = () => { //作られるitemの定義 const newItem = { itemName: inputValue, quantity: 1, isSelected: false, }; //items配列にpushされる const newItems = [...items, newItem]; //useStateのitemsに反映 setItems(newItems); //入力値を空に setInputValue(""); };まずは
const newItem{}
にて新しく作られる買い物リストのオブジェクトの初期値を定義しています。
itemNameはアイテムの名前、quantityはアイテムの個数、isSelectedはtodoかdoneかを値として持っています。
itemName: inputValue,
では、先ほど常に入力値が反映される様になったinputValue
を新規買い物リストの名前にの部分に入れています。const newItems = [...items, newItem];ではスプレット構文を使って、itemsのオブジェクトの最後にnewItemを入れたnewItemsという配列を作っています。買い物リストを新規追加した後は更新関数setItem()を使って、反映させています。
クリック時に処理はできたので、+ボタンをクリックした際に処理が走る様にします。
<FontAwesomeIcon icon={faPlus} onClick={() => handleAddButtonClick()} />これにて、入力値を入れて、+ボタンを押せば、買い物リストが新規追加されます。
3. アイテムの完了/未完了を切り替える
さきほどのmapの配列時に書いてあったindexを引数に用いて、特定のタスクの状態を切り替えます。
この様な処理を書いてください。//done切り替え const toggleComplete = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //引数にindexから、該当するitemのisSelectedを切り替える newItems[index].isSelected = !newItems[index].isSelected; setItems(newItems); };
const newItems = [...items];
を行う事で、itemsオブジェクトを展開してnewItemsという配列にしています。
配列にしたのでindexを用いて特定のアイテムのisSelected
まで到達しています。newItems[index].isSelected = !newItems[index].isSelected;処理としてはisSelectedのboolean型を切り替えていますね。
toggleCompleteの処理ができたので、今まで同様にクリックした際に発火させます。
<div className="item-name" onClick={() => toggleComplete(index)}>これにてtodo⇄doneの切り替えの機能は完成です。
4. アイテムの個数を増減できる様にする
実はやり方はほとんど、3の完了未完了の切り替えと同じです。
クリック時に起こる処理をuseStateの更新を使って書き、ボタンをクリックした際にそれが発火する様にします。一気にコードを加えていきましょう。
まずは増える際の処理。
itemsオブジェクトを展開して、indexから特定のアイテムの数量を+1しています。const handleQuantityIncrease = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //quantityに+1する newItems[index].quantity++; setItems(newItems); };次に減る際の処理。ほとんど一緒ですね。
const handleQuantityDecrease = (index) => { //itemsを展開した配列、newItemsを作る const newItems = [...items]; //quantityに-1する newItems[index].quantity--; setItems(newItems); };増えるイベントと、減るイベントをボタンから発火させます。
<button> <FontAwesomeIcon icon={faChevronRight} onClick={() => handleQuantityIncrease(index)} /> </button><button> <FontAwesomeIcon icon={faChevronLeft} onClick={() => handleQuantityIDecrease(index)} /> </button>これにて増減の機能は完成です。
5. 総量を表示する
総量の表示もuseStateを使って、アイテムの数が増減する、アイテムが新規で作られるといった時に、数が更新される様にします。
まずはuseStateを使って、定義を作ります。
const [totalItemCount, setTotalItemCount] = useState(0);トータル表示のところでは、useStateにて定義した
totalItemCount
を入れていきます。<div className='total'>Total: {totalItemCount}</div>次に、最新の総量を計算する処理を書きます。
const calculateTotal = () => { const totalItemCount = items.reduce((total, item) => { return total + item.quantity; }, 0); setTotalItemCount(totalItemCount); };
items.reduce((total, item) => {return total + item.quantity;}, 0);
ではreduceを使う事で、itemsオブジェクトからitem.quantityを1つずつ抜き取り、totalにその値を加えるということを繰り返すことで、item.quantity
の合計値を出しています。最後に
calculateTotal
を個数が変わる処理の最後に入れて、再計算を毎回行える様にしてください。。
こんな感じになります。const handleAddButtonClick = () => { // ...other code calculateTotal(); }; const handleQuantityIncrease = (index) => { // ...other code calculateTotal(); }; const handleQuantityDecrease = (index) => { // ...other code calculateTotal(); };ついに完成!!
お疲れ様です。
皆さんの手元でも、この様にちゃんと動作していますか??上手くいかなくてどうしても原因がわからない時は、github内にfinalというフォルダがあり、完成形のコードが入っているので参考にしてみてください。
まとめ
このチュートリアルではuseStateで、items、inputValue、totalItemCounと3つ状態管理をおこなっていきました。
実際に手を動かしuseStateを扱うことで、関数コンポーネントで状態を扱っていくことのイメージは掴めたと思います。参考になったらLGTMしてもらえると嬉しいです。
Twitterやブログもやっているので、興味あればご覧になってください!ではまた。。