20201115のReactに関する記事は11件です。

サーバーレスな LGTM 画像生成サービスを作ってみた

作ったもの

ユーザー登録不要のシンプルな LGTM 画像生成サービスです。

LGTM Generator

ローカルから画像をアップロードして LGTM 画像を生成することができます。
GIF アニメーションにも対応しています。
画像検索機能を利用して好きな画像から LGTM 画像を生成することもできます。
upload.gif
生成された LGTM 画像の画像リンクを簡単にクリップボードにコピーすることができます。
リンクの形式は HTML と Markdown で好きな方を選択できます。
copy_2.gif

構成

バックエンドは 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 の設定 ~ 使用方法については以下のドキュメントを参考にしました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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」が必要なので、順番にインストールしていきます。

手順は以下の通りです。

  1. Homebrewをインストールする
  2. nodebrewをインストールする
  3. Node.jsをインストールする
  4. npmをインストールする
  5. create-react-appを実行する

1つずつクリアしていきましょう。

1. Homebrewのインストール

Homebrew」とはmacOSまたはLinux用のパッケージマネージャーです。

Homebrewの公式サイトはこちらです。

Homebrewの公式サイトのホームページにあるコマンドをターミナルに貼り付けて実行します。

59236a60952021c751c1c00fca393d5d-2048x1475.png

実行するとインストールが始まりますので、しばらく待ちます。

インストールが完了したら、以下のコマンドを実行してHomebrewのバージョンを確認してみましょう。このコマンドは、Homebrewのバージョンを確認するコマンドです。

% brew -v

e7bbdc97dd0a936ad62e33a6dcc4889b.png

今回は、バージョン2.4.0がインストールされています。
このように、Homebrewのバージョンを確認することができたら、インストールは正常に完了しています。

2. nodebrewのインストール

nodebrew」とはNode.jsのバージョン管理システムで、Homebrewでインストールすることができます。
nodebrewはnodeをインストールするために使います。

ターミナルで以下のコマンドを実行します。

% brew install nodebrew

しばらく待ちます。

Homebrewをインストールした際と同様に、nodebrewのバージョンを確認します。
以下のコマンドを実行しましょう。

% nodebrew -v

db10c4caef44d3c43402bc5a843d7c29.png

このようにnodebrewのバージョンが表示されれば、nodebrewのインストールは完了です。

3. Node.jsをインストールする

Node.js」とは、簡単にいうとサーバーサイドで動くJavaScriptのことです。

Node.jsをインストールしていきます。
まず、インストール可能なNode.jsのバージョンを確認します。以下のコマンドを実行してください。

% nodebrew ls-remote

コマンドを実行すると、次の画像のようにインストールすることができるNode.jsのバージョンが一覧で表示されます。この中から、好きなバージョンを選んでインストールすることができます。

399245e4fa2420f82357ab2472acbc17.png

create-react-appに必要なNode.jsのバージョンは8.1.0以上なので、インストールするバージョンはこれより新しいバージョンであれば問題ありません。

では、インストールしていきます。
以下のコマンドを実行します。

% nodebrew  install stable

このコマンドでは、Node.jsの安定バージョンをインストールすることができます。stableが、安定バージョンという意味です。

stableの部分をlatestにすると最新バージョンをインストールすることができます。
最新バージョンはバグなどが起きやすいので、基本的には安定バージョンのインストールで大丈夫です。

インストールしたNode.jsを確認します。以下のコマンドを実行しましょう。

% nodebrew ls

c435e3d78fdca8249a50d81aa81ce1a7.png

コマンドを実行すると。画像のように現在インストール済みの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

4bf0687101a47200fb682211b660796e.png

先ほどcurrentに設定したバージョンが表示されれば、Node.jsのインストールは完了です。

4. npmをインストールする

npmをインストールしていきます。npmとは、Node Package Managerの略で、Node.jsのパッケージを管理するものです。
パッケージとは、Node.jsの便利な機能をまとめたものです。これを簡単に管理できるのがnpmになります。

とは言っても、実はNode.jsをインストールした時点でnpmも一緒にインストールされています。
以下のコマンドで確認してみましょう。

% npm -v

afbfc217f33f8e10780d8e083a742bfb.png

このように、バージョンが表示されれば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」としました。

コマンドを実行して、しばらく待ちます。

df4cdfad1c8e7697f479ea7de384941d.png

処理が完了したら環境構築は終了です!
作成したフォルダ内でReactを使う準備ができています。

この後は、実際にブラウザに表示させるまでをやってみたいと思います。

エディタで開く

以下のコマンドを実行してエディタで作ったフォルダを開きます。(ぼくはVScodeをエディタとして使っています)

% code practice

エディタで開くとsrcフォルダやpublicフォルダなど色々なフォルダとファイルが自動的に作成されています。

50f8fb23414b32ac263bade579da38c1.png

画像はsrcフォルダ内のApp.jsファイルを開いています。

ブラウザに表示する

エディタ内でターミナルを起動し、作業を続けます。

以下のコマンドを実行して、buildフォルダを作成します。コマンドを実行する際は、必ず開発フォルダに移動してから実行してください。

% npm run build

コマンドを実行すると、画像のようにbuildフォルダが作成されます。
このフォルダの中にあるindex.htmlが実際にブラウザに公開するファイルです。

npm run buildではsrcフォルダとpublicフォルダの中のファイルを一つにまとめてbuildディレクトリに出力しています。

buildフォルダが作成できたら、以下のコマンドを実行します。

% npm start

50ae8c04ea4c92c2ac42c91818a2b625-2048x1475.png

このコマンドを実行するとブラウザが立ち上がり、create-react-appで作成した初期の画面が表示されます。

npm startではローカルサーバーを起動してReactアプリを確認することができるようになります。

Reactでは、srcフォルダ内のApp.jsファイルを書き換えていくことでアプリケーションの中身が変わっていきます。

まとめ

今回はcreate-react-appを使ってReactの環境構築をしました。
1コマンドでReact開発環境を超カンタンに構築できたと思います。

Reactの公式ドキュメントによると、

「Create React AppはReactを学習するのに最適な環境であり、Reactで新しいシングルページアプリケーションを作成するのに最も良い方法です」
引用 : React公式ドキュメント

と書かれています。

Reactは、現在人気がある言語なのでぜひ勉強してみてください!

最後まで読んでいただいてありがとうございました。ではまた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでinputの値を更新するたびにフォーカスが外れる

inputをonChangeで更新するとフォーカス外れる

超初歩的なミスなんですが、今まで何度かやってしまっています笑

自分がやってしまうのは、mapでリストをレンダーする際に、onChangeで更新するvalueをkeyの一部にしてしまっているパターンです。

誤った例

List.js
const 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.js
const 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で変更されないようにしましょう。

参考:React.js loses input focus on typing

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 学習

React

今後のフロントエンドのデファクトスタンダードになるReact

日本国内ではVue.jsの勢いがあるが
今後はReactがくると思っているので自分はReact学びます。

誰もが使う技術を使うっていうのは非常に自分としては大事にしています。

  • その技術が使える人がいることで保守運用できる。
  • 採用する際にも人が集まる。
  • 技術の情報が集まる

などなど非常にメリットが多いと思っています。

component志向の言語となるので
画面に必要なパーツを分散させて保守性をあげるという非常にメリットがあるものになります。

自分が新規で開発しているサービスで簡単に解説できたらなと思ってます。

App.tsx
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)


    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;

公正としては

スクリーンショット 2020-11-15 13.57.42.png

こんな感じになってます。

一番わかりやすいのがhomeだと思うのですが

  • Home.tsx
  • timeLine.tsx
  • timeLineModal.tsx

これに加えて全ての画面で共通して使う場所として
App.tsx

もあります。

の三つのコンポーネントが合わさって初めて
Home画面ができるというイメージです。

App.tsx
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)


    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
import 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.tsx
import 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.tsx
import 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 timeLineModal

hooks

従来のReactでは状態管理といえばReduxしかなかったため
それが使われていたイメージです。

一方で今日ではhooksという技術も使われています。

Reduxに関しては死ぬほどAngular内で学ぶことになりそうなので一旦Hooksの理解に努めます。

Hooksは基本的に親のComponentで定義した値をその
子供のComponentの中でも使えるようにする技術だと自分は理解しています。

これを使うことで
全てのComponentで同じ値を用いることができるというのが利点ですよね。

それぞれのComponent
で異なる値を用いていた場合、非常に面倒な処理が待っているのが地獄なので
状態管理という手法が今日では用いられています。

hooksの中でも

  • useState
  • useEffect
  • useContext

の3つを簡単にまとめられたらと思っています

App.tsx
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)


    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')
    })    
})

   ...

というファイルの場合

下記の画像のように
スクリーンショット 2020-11-15 15.03.36.png

render start

render finishedの順に描画されていることがわかるかと思います。

APIで画面描画後に何かしら処理したい
時などに用いられます。

また第二引数にcontextを指定して指定したparamが変わった場合にだけ
effectを実行するという指定もできます。

全ての変更で全く違う処理が走ると大変ですからね。

createContextを使う

createContext

export const RequirementContext = createContext(requirementInfo)

createContextで定義することによってcontextが生成されます。

contextとは言うなればglobalに使えるstateという認識になります。

親で定義した共通の値を子コンポーネントで用いるためにまず準備してあげます。

useContextを使う

RequirementDetail.tsx
import 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やスプレッド構文などで対応してあげる必要があります。

簡単にまとめてみましたが
まだまだ自分も使いこなせるわけではないのでこれからも精進していきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
      const 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.js
      const client = new Twitter({
        consumer_key: vz***** /** consumer key */,
        consumer_secret: mT***** /** consumer secret */,
        access_token_key: 11*****,
        access_token_secret: 0d*****
      });

(TwitterAPIをNodeで何かしたい時は、Twitterライブラリ使うのがおすすめです。)

以上です。何かのお役にたてば幸いです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactでnpm ERR! code E404が出た時の対処方法

Reactでnpx create-react-app コマンドを使用した際に出たエラー

コード[ 'creat-react-app@latest' ]で1のインストールに失敗しました

解決方法

む、よくみるとおかしい・・・

ダメ絶対!

creat-react-app

create-react-app

createはeをチャントつけること・・・。
今回は1分で解決。

でも忘れないように投稿しとく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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}にもその変更が反映されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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の扱い方の基礎

チュートリアルの完成像

project.gif

解説編

React Hooksとは

React Hooksとは2019年2月からReactに追加された機能です。
これを使う事で、クラスを使わずに関数コンポーネントだけでもstateを扱うことが出来る様になりました。

関数コンポーネントで書くことで、クラスコンポーネントより処理がまとめやすくスッキリしたフォルダ構成に出来るなど、様々な恩恵が受けられます。

その他も様々な違いがあります。詳しくはこちらの記事などを参考にしてください。
https://tyotto-good.com/blog/reaseons-to-use-function-component

useStateとは

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の基本的な使い方を解説したところで、実際に買い物リストのチュートリアルに入りましょう。

改めて一度完成像を見ておきましょう。
project.gif

チュートリアルの手順

  1. スタートの状態を作る
  2. 新しいアイテムを追加する
  3. アイテムの完了/未完了を切り替える
  4. アイテムの個数を増減できる様にする
  5. 総量を表示する

1. スタートの状態を作る

まずは手物で環境を作りましょう。
僕のgithubアカウントに初期状態を作っておきましたので、git cloneして手元に持ってきてください。
https://github.com/gunners6518/shopping-list

starterフォルダ配下のApp.jsに実際のコードを書いていきます。
git cloneができたら、yarn installyarn startを行いブラウザで正しく表示されているかを確認しましょう。

スクリーンショット 2020-11-15 1.53.03.png

2. 新しいアイテムを追加する

Add an itemに値を入力して、+ボタンを押すことで、買い物リストにアイテムが新規追加される様にします。
大まかな流れとしては以下の通りです。

  • 買い物リストを表示する
  • 入力値が変わるたびに、その値を保存する
  • +ボタンを押した際に、アイテムを買い物リストに新規追加する

では進めていきましょう。

買い物リストを表示する

まずは買い物リストを表示できる様にしましょう。
といっても初期状態では、買い物リストは0個なので、表示自体は出来ないですが、、

買い物リストの一覧表示と聞くと、多くの人がfor文を想像するかもしれませんが。
Reactではmapを使います。(Reactではというより、正確には関数コンポーネントでは)

mapの使い方について不安な方はこちらの記事が参考になります。
https://qiita.com/ryosuketter/items/c11a58242fa1ad9c6e4c

returnの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();
    };

ついに完成!!

お疲れ様です。
皆さんの手元でも、この様にちゃんと動作していますか??

project.gif

上手くいかなくてどうしても原因がわからない時は、github内にfinalというフォルダがあり、完成形のコードが入っているので参考にしてみてください。

まとめ

このチュートリアルではuseStateで、items、inputValue、totalItemCounと3つ状態管理をおこなっていきました。
実際に手を動かしuseStateを扱うことで、関数コンポーネントで状態を扱っていくことのイメージは掴めたと思います。

参考になったらLGTMしてもらえると嬉しいです。
Twitterブログもやっているので、興味あればご覧になってください!

ではまた。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 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の扱い方の基礎

チュートリアルの完成像

project.gif

解説編

React Hooksとは

React Hooksとは2019年2月からReactに追加された機能です。
これを使う事で、クラスを使わずに関数コンポーネントだけでもstateを扱うことが出来る様になりました。

関数コンポーネントで書くことで、クラスコンポーネントより処理がまとめやすくスッキリしたフォルダ構成に出来るなど、様々な恩恵が受けられます。

その他も様々な違いがあります。詳しくはこちらの記事などを参考にしてください。
https://tyotto-good.com/blog/reaseons-to-use-function-component

useStateとは

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の基本的な使い方を解説したところで、実際に買い物リストのチュートリアルに入りましょう。

改めて一度完成像を見ておきましょう。
project.gif

チュートリアルの手順

  1. スタートの状態を作る
  2. 新しいアイテムを追加する
  3. アイテムの完了/未完了を切り替える
  4. アイテムの個数を増減できる様にする
  5. 総量を表示する

1. スタートの状態を作る

まずは手物で環境を作りましょう。
僕のgithubアカウントに初期状態を作っておきましたので、git cloneして手元に持ってきてください。
https://github.com/gunners6518/shopping-list

starterフォルダ配下のApp.jsに実際のコードを書いていきます。
git cloneができたら、yarn installyarn startを行いブラウザで正しく表示されているかを確認しましょう。

スクリーンショット 2020-11-15 1.53.03.png

2. 新しいアイテムを追加する

Add an itemに値を入力して、+ボタンを押すことで、買い物リストにアイテムが新規追加される様にします。
大まかな流れとしては以下の通りです。

  • 買い物リストを表示する
  • 入力値が変わるたびに、その値を保存する
  • +ボタンを押した際に、アイテムを買い物リストに新規追加する

では進めていきましょう。

買い物リストを表示する

まずは買い物リストを表示できる様にしましょう。
といっても初期状態では、買い物リストは0個なので、表示自体は出来ないですが、、

買い物リストの一覧表示と聞くと、多くの人がfor文を想像するかもしれませんが。
Reactではmapを使います。(Reactではというより、正確には関数コンポーネントでは)

mapの使い方について不安な方はこちらの記事が参考になります。
https://qiita.com/ryosuketter/items/c11a58242fa1ad9c6e4c

returnの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();
    };

ついに完成!!

お疲れ様です。
皆さんの手元でも、この様にちゃんと動作していますか??

project.gif

上手くいかなくてどうしても原因がわからない時は、github内にfinalというフォルダがあり、完成形のコードが入っているので参考にしてみてください。

まとめ

このチュートリアルではuseStateで、items、inputValue、totalItemCounと3つ状態管理をおこなっていきました。
実際に手を動かしuseStateを扱うことで、関数コンポーネントで状態を扱っていくことのイメージは掴めたと思います。

ではまた。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【 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の扱い方の基礎

チュートリアルの完成像

project.gif

解説編

React Hooksとは

React Hooksとは2019年2月からReactに追加された機能です。
これを使う事で、クラスを使わずに関数コンポーネントだけでもstateを扱うことが出来る様になりました。

関数コンポーネントで書くことで、クラスコンポーネントより処理がまとめやすくスッキリしたフォルダ構成に出来るなど、様々な恩恵が受けられます。

その他も様々な違いがあります。詳しくはこちらの記事などを参考にしてください。
https://tyotto-good.com/blog/reaseons-to-use-function-component

useStateとは

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の基本的な使い方を解説したところで、実際に買い物リストのチュートリアルに入りましょう。

改めて一度完成像を見ておきましょう。
project.gif

チュートリアルの手順

  1. スタートの状態を作る
  2. 新しいアイテムを追加する
  3. アイテムの完了/未完了を切り替える
  4. アイテムの個数を増減できる様にする
  5. 総量を表示する

1. スタートの状態を作る

まずは手物で環境を作りましょう。
僕のgithubアカウントに初期状態を作っておきましたので、git cloneして手元に持ってきてください。
https://github.com/gunners6518/shopping-list

starterフォルダ配下のApp.jsに実際のコードを書いていきます。
git cloneができたら、yarn installyarn startを行いブラウザで正しく表示されているかを確認しましょう。

スクリーンショット 2020-11-15 1.53.03.png

2. 新しいアイテムを追加する

Add an itemに値を入力して、+ボタンを押すことで、買い物リストにアイテムが新規追加される様にします。
大まかな流れとしては以下の通りです。

  • 買い物リストを表示する
  • 入力値が変わるたびに、その値を保存する
  • +ボタンを押した際に、アイテムを買い物リストに新規追加する

では進めていきましょう。

買い物リストを表示する

まずは買い物リストを表示できる様にしましょう。
といっても初期状態では、買い物リストは0個なので、表示自体は出来ないですが、、

買い物リストの一覧表示と聞くと、多くの人がfor文を想像するかもしれませんが。
Reactではmapを使います。(Reactではというより、正確には関数コンポーネントでは)

mapの使い方について不安な方はこちらの記事が参考になります。
https://qiita.com/ryosuketter/items/c11a58242fa1ad9c6e4c

returnの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();
    };

ついに完成!!

お疲れ様です。
皆さんの手元でも、この様にちゃんと動作していますか??

project.gif

上手くいかなくてどうしても原因がわからない時は、github内にfinalというフォルダがあり、完成形のコードが入っているので参考にしてみてください。

まとめ

このチュートリアルではuseStateで、items、inputValue、totalItemCounと3つ状態管理をおこなっていきました。
実際に手を動かしuseStateを扱うことで、関数コンポーネントで状態を扱っていくことのイメージは掴めたと思います。

参考になったらLGTMしてもらえると嬉しいです。
Twitterブログもやっているので、興味あればご覧になってください!

ではまた。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む