20210403のReactに関する記事は13件です。

Material UIのData Gridでブラウザーバックに対応する

初めに

に関するTipsです。

本題

DataGridって便利ですよね。
React × Material UIで言うと、下記のようなライブラリがあります。

DataGridを使うことで、

  • ページネーション
  • フィルター
  • ソート

などが簡単に実装できてしまいます。ただ、
ブラウザーバックをするとページやフィルター、ソートがリセットされてしまう。
というのを問題の一つと感じています。(特にデータ件数が詳細画面への遷移も多いAdmin系の画面だとより一層感じます。)

その解決の解決策として、2つあるかと思います。

解決案その1

reduxなどのステート管理の仕組みを利用し、ページやフィルター情報を画面毎に保持しておき
再度画面が表示された際にページやフィルターの初期値として、ステートの情報をセットします。
ただ、この方法だと

  • 画面毎にステートを管理する必要があり共通化しずらい。
  • 通常の遷移の場合でもステートからセットされてしまう為、ステートのリセットや無視する実装が必要。

という問題があります。

解決案その2

URLパラメータにページやフィルター情報を含め、ページ操作などが行われた際にURLをreplaceします。
この方法だと、

  • 共通化がし易い。
  • ステート管理が不要。
  • ページやフィルターを含めた状態のURLをブックマークやシェア出来る。

というメリットがあります。

実装方法

下記コードがその実装方法になります。

ポイントは、
DataGridのonPageChangeやonSortModelChangeのイベントを受け取り、
そのデータをqsというライブラリーを使用してURLに変換しています。
同様にqsを使ってURLからデータを復元し、DataGridにセットします。
※ 脆弱性は未検証です。

各画面では、このDataGridを利用するだけで特に意識することなくブラウザーバックによる
ページやソートの復元が実現できます。

URLのイメージはこんな感じになります。

Apr-03-2021 23-46-41.gif

ブラウザーバックでは一覧の状態が保持され、リンクをクリックした場合は初期状態になっています。

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

SPAって何?

Reactを勉強しててSPAという言葉よく聞くようになり、なんとなくの理解だった為改めて文字に起こしてアウトプットしていきたいと思います!

一番下に参考にした記事を載せておきます!

SPAってなに?

SPAとはSingle Page Application(シングルページアプリケーション)のことです。
簡潔にまとめると、必要な部分だけ更新して、必要ないところはそのままの表示にすることです!

今までのwebアプリケーションの仕組みは、何かアクション(クリックなど)をすると、
①サーバーにリクエスト
②サーバー側でHTMLを生成
③②で生成されたHTMLを受け取り、ブラウザで描画する
流れでした。

従来の仕組みだと不要なデータまで更新される為、表示するまでに時間がかかっていました。

そこでSPAの登場!!

①ユーザーがアクションを起こす
②①のアクションに必要なものだけをサーバーにリクエスト
③帰ってきたデータをJSで処理してHTMLで表示する
流れに変わりました!

SPAの仕組みを利用することでユーザーのサービス利用時間が長くなる=購入確率などが高くなる=会社の売り上げに貢献!!←少し飛躍しすぎました。。。

だから今SPAへの温度感が非常に高まってるとか。。。

その他メリット

他のメリットデメリットについては他記事でも乗ってるので興味のある方はそちらを参考にしてみてください!
ここではざっくり説明していきます

①よりリットなweb表現ができる

ReactやVue.jsなどのライブラリやフレームワークが誕生したことによって、簡単に様々な物が実装可能なり、いろんなUIを表現することができるようになりました!

②ネイティブアプリの代用ができる

ここに関しては自分自身が体験してない為、あまり実感が湧きませんがSPAを導入することで
ネイティブアプリで実装されてたことがSPAでも表現することが可能とのことです!!←いずれここも体験してみたいです!

最後に

どうやらSPAは何かを実装する上でデファクトスタンダードそう。これからもSPAを意識しながら学習に取り組んでいきたいと思います!!

参考記事
https://rara-world.com/spa-single_page-merit/
https://www.oro.com/ja/technology/001/

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

Reactのコンポーネントについてまとめてみた

Reactを触っていると『コンポーネント』という言葉を頻繁に耳にします。
どのようなことか概念は分かっているのですが、以下に言語化して理解を深めたいと思います。

コンポーネントとは?

コンポーネントを直訳すると『構成要素』『部品』や『コンピュータ機器やソフトウェアの部品』『ステレオで、チューナー・アンプ・プレーヤー・スピーカーなどの単独の機器』のことを表しますが、Reactでは『UI の一部分となるビュー (View) を切り出したもの』を表しています。

そもそもUIって何のことか曖昧だったのでついでに調べてみました。
UI(ユーザーインターフェイス)はユーザー(利用者)と製品やサービスとのインターフェース(接点)すべてのことを意味するということ。
具体的にはGoogleのトップページは大変シンプルでユーザーに分かりやすいように設計されているので優れているとUI言えます。そもそも今、目に入っているwebページもUIです。

コンポーネント化するメリットは何か?

コンポーネント化をすることのメリットは何があるのか。
それはViewを切り出したものを使い回せることが一番大きいと思います。
コンポーネントごとにjs/jsxファイルを用意して、それを組み合わせてUIを構築・開発していきます。例えばボタンをコンポーネント化させることで、トップページで使ったボタンをユーザーページでも再利用することができます。つまり一つコンポーネントを作っておけば、そのコンポーネントを呼び出せば簡単に使い回すことができるということです。
(ちなみにHeaderやFooterなどもコンポーネント化させることができます!)

これによりコードの記述量が減ったり、メンテナンス性が向上したりします。
他にもメリットはありますが以上のことからViewを切り出したものを使い回せることが一番大きいと思います。

最後に

コンポーネントをもっと深く知りたい方はぜひ公式のドキュメントの三目並べを作ってもらうのが一番理解出来るかと思います!

コンポーネントと再レンダリングの関係性もとても大切なので、こちらもゆくゆくは記事にしたいです。
また内容に不明点などありましたらコメントいただければ幸いです。

参考文献

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

Reactのコンポーネントのメリットは何か。

Reactを触っていると『コンポーネント』という言葉を頻繁に耳にします。
どのようなことか概念は分かっているのですが、以下に言語化して理解を深めたいと思います。

コンポーネントとは?

コンポーネントを直訳すると『構成要素』『部品』や『コンピュータ機器やソフトウェアの部品』『ステレオで、チューナー・アンプ・プレーヤー・スピーカーなどの単独の機器』のことを表しますが、Reactでは『UI の一部分となるビュー (View) を切り出したもの』を表しています。

そもそもUIって何のことか曖昧だったのでついでに調べてみました。
UI(ユーザーインターフェイス)はユーザー(利用者)と製品やサービスとのインターフェース(接点)すべてのことを意味するということ。
具体的にはGoogleのトップページは大変シンプルでユーザーに分かりやすいように設計されているので優れているとUI言えます。そもそも今、目に入っているwebページもUIです。

コンポーネント化するメリットは何か?

コンポーネント化をすることのメリットは何があるのか。
それはViewを切り出したものを使い回せることが一番大きいと思います。
コンポーネントごとにjs/jsxファイルを用意して、それを組み合わせてUIを構築・開発していきます。例えばボタンをコンポーネント化させることで、トップページで使ったボタンをユーザーページでも再利用することができます。つまり一つコンポーネントを作っておけば、そのコンポーネントを呼び出せば簡単に使い回すことができるということです。
(ちなみにHeaderやFooterなどもコンポーネント化させることができます!)

これによりコードの記述量が減ったり、メンテナンス性が向上したりします。
他にもメリットはありますが以上のことからViewを切り出したものを使い回せることが一番大きいと思います。

最後に

コンポーネントをもっと深く知りたい方はぜひ公式のドキュメントの三目並べを作ってもらうのが一番理解出来るかと思います!

参考記事・サイト

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

【とりあえずハンズオン】AWS AmplifyでReactやってみた

はじめに

この記事では
AWS Amplify を利用して React を実行してみたハンズオンの記事です。
ベストプラクティスや間違いがあれば
書き直していく予定です。

AWS Amplify とは

ウェブアプリケーションを素早く構築してくれる AWS のサービス
数多くの言語がサポートされている。

Amplify では、一般的なウェブフレームワーク (JavaScript、React、Angular、Vue、Next.js) や
モバイルプラットフォーム (Android、iOS、React Native、Ionic、Flutter) がサポートされています。
AWS Amplify で迅速な市場投入を実現してください。

最近、SPA 化が流行り出したこともあって
脚光を浴びているサービスに見えなくもないので実際どんなもんか
今回はハンズオンしてみた。

結論

めっちゃ使いやすい!!

「嘘でしょ!?」ってくらい簡単にできてしまったのでハンズオンした記録を残しておきたい。

参考にしたページ

モジュール 1

モジュール 2

モジュール 1 をハンズオン

React レポジトリを作成

AWS Amplify で利用するレポジトリを GitHub から取得する為
GitHub に push する用のアプリケーションを作成する。

create-react-app amplifyapp

ディレクトリをチェンジして
ローカルホストで動作確認を行う。

cd amplifyapp
yarn start

しばらくすると、ぬるぬる動く React おなじみの絵が表示される。

amplify_end.JPG

React レポジトリを GitHub に push

動作確認が終わったら一旦、アプリケーションを止める。

git init
git add .
git commit -m "initial commit"
git branch -M main
git remote add origin git@github.com:username/amplifyapp.git
git push -u origin main

※username には自分の GitHub アカウント ID を利用

AWS Amplify 開く

GET STARTED をクリックするとスクロールが下に案内される。
amplify_get._startJPG.JPG

Develop と Deliver の 2 つがあるので「Host your web app」の「GET STARTED」をクリック

amplify_develop.JPG

Host your web app

ホストするアプリケーションを Github から取得する為
「GitHub」を選択

amplify_host_webapp.JPG

GitHub 認証済みだと以下のようになるが、まだ認証していない人は
ユーザ名とパスワードが聞かれ、アプリケーションを認証するかどうか聞かれます。

amplify_branch_connect.JPG

ホストするレポジトリ名とブランチ名を選択肢して「次へ」

ビルド設定の構成

これはとりあえずデフォルトでヨシッ。

amplify_build.JPG

迷わずに「次へ」

保存してデプロイ

保存してデプロイ
amplify_deploy.JPG

「保存してデプロイ」をクリックすると。。。

amplify_build_success.JPG

プロビジョニング、ビルド、デプロイ、検証というプロセスを踏んで
デプロイ作業が始まる。

デプロイが終わると Amplify が生成した URL から
作成したアプリケーションを実行できる。

amplify_end.JPG

AWS Amplify の凄いところ

手軽さに尽きる。
お手元のアプリケーションをサッとデプロイすることで
あとは良しなにやってくれる。

そして、デプロイに成功したアプリ群はすべてのアプリから一覧で見ることができる。
今まで作ったアプリを容易に管理できるってわけ。

さらに、チョット人に見せる程度の用途なら
インフラストラクチャの構築がいらないのは凄い。
※厳密にいえば、AWS Amplify が良しなにやってくれてる。

GitHub との連携も効いているので CircleCI とも疎結合に連携できる。

何でもかんでも出来すぎて。。。出木杉君になったわ。

でも、お高いんでしょ?

お値段はこちら
https://aws.amazon.com/jp/amplify/pricing/

2021/04/03(土) 現在では

Amplify フレームワーク
Amplify フレームワーク (ライブラリ、CLI、UI コンポーネント) を使用する場合は
基盤として使用する AWS のサービスに対してのみお支払いいただきます。
Amplify フレームワークの使用には、追加料金は発生しません。

静的ウェブホスティング
AWS Amplify コンソールでは、2 つの機能に対して料金が設定されています。
ビルド & デプロイ、およびホスティングです。
ビルド & デプロイ機能の場合、ビルド分あたりの料金は 0.01USD です。
ホスティング機能の場合、対象の GB ごとの料金 0.01USD と保存された GB ごとの価格は 0.01USD です。

AWS 無料利用枠を使用すると、無料で使い始めることができます。
サインアップすると、新規の AWS 顧客はビルドとデプロイ機能で 1 か月あたり 1,000 ビルド分、
ホスティング機能で 1 か月あたり 15 GB の提供**と 1 か月あたり **5 GB のデータストレージを受け取ります。

つまりどういうことか

  • Amplify フレームワーク とやらを利用すると料金が発生
  • ビルドアンドデプロイするとビルド分だけ料金が発生
  • 静的ウェブホスティングはGB分だけ料金が発生
  • 無料利用枠ならイイ感じに施しを受けられる

おや?これはポートフォリオの公開にうってつけでは?

まとめ

今回は AWS Amplify を使って React アプリケーションをデプロイしました。
使い方を変えればノーコードアプリケーションもデプロイできるんかなこれ。
だとしたら結構優れものだぞ。

おわり

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

jsx,tsxでEmmetを使用する方法

はじめに

ReactでEmmetが使えたら便利だな〜って思って調べたら利用出来たのでご紹介します。
VScodeだと素の状態でもEmmetがインストールされているのでおすすめです。

方法

方法はsetting.jsonを弄るだけ。
command + , (Windows: Ctrl + ,)で設定を開きます。
検索にsetting.jsonと入れてsetting.jsonを編集をクリック

その後setting.json

setting.json
{
  "emmet.triggerExpansionOnTab": true,
  "emmet.includeLanguages": {
        "javascript": "javascriptreact",
        "typescript": "typescriptreact",
    },
}

を入れればtsx,jsxでEmmetが使用できます。

さいごに

Emmetを愛していたのでEmmetが使用できない環境は苦痛でしたが解決しました。
Emmetを使い慣れてる人にはおすすめです。

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

フロントエンド、バックエンド、モバイルアプリのボイラープレートを公開します。

弊社で作成中のWebアプリ、モバイルアプリ用のボイラープレートを公開します。

公開した経緯

作ったは良いものの、実プロジェクトで使うかは微妙です。
まだまだ完成度は低いですが、完成せずに放置する可能性も出てきました。
個人の時間も使って作ったので、少しでも役立てたいと思い公開の許可を頂きました。

使用している主な技術

使用している主な技術はこんな感じです。

環境 言語 フレームワーク
Backend PHP Laravel
Frontend Typescript React, Redux
Native Dart Flutter, Provider

注意点

  • LaravelやFlutterは初めて使ってます。
    • 未熟な部分も多々ありますので、優しく教えていただければ幸いです。
  • 実プロジェクトでまだ利用してませんので、有用かは分かりません。
  • ボイラープレートを良いことに雑な箇所もあります。。

主な特徴

  • テスト、静的解析自動化 (Github Actions)
    • Backend -> PHPStan, PHP_CodeSniffer, PHPUnit
    • Frontend -> ESLint, Typescriptのビルド
    • Native -> Flutter format, Flutter test
  • ステージング環境へのデプロイ自動化 (Github Actions)
    • Backend -> Heroku
    • Frontend -> Firebase Hosting
    • Native -> Firebase App Distribution
  • ステージング環境は激安運用可能 (Freenom, CloudFlare)
  • 多言語、タイムゾーンを考慮 (検証不十分です。特にタイムゾーン。)
  • フロントエンドやモバイルとバックエンドの繋ぎ込みを極力共通化
    • API呼び出しのローディングやサーバーサイドのエラー反映も共通化

詳細

今後、各アプリの詳細や得られた知見を記事にできたらなと思います。

画面イメージ

ボイラープレートですので、あえて(?)シンプルです。

Webアプリ

Apr-03-2021 16-55-33.gif

モバイルアプリ

Apr-03-2021 17-30-40.gif

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

Reactでページ内遷移のある横スクロールスライドメニューを実装する

はじめに

スマホ向けのWebアプリケーションをReactで開発していて、横スクロール型のスライドメニュー(ナビゲーション)を作りたい!という時にご参考ください。 React×Railsで開発しています。

HTMLのアンカータグで#を使って特定のid属性に遷移させたい!それをReactでやりたい!みたいな時を想定。

やりたいこと

  1. アプリライクな横スクロール型のメニューを作りたい(スクロールバー非表示にして)
  2. メニューの各要素をクリックしたらそれぞれの要素までぬるっとアニメーション付きでページ内遷移させたい
  3. ヘッダーの高さを考慮してページ内遷移させたい
  4. mapではき出した動的な要素にid属性を振って遷移先にしたい

方針

Reactでページ内遷移するいい方法ないかなーと探していたところ、こちらの記事(Reactでページ内リンクを実装する)を発見。今回はこちらを参考にreact-router-hash-linkを使うことに。

Githubはこちら:React Router Hash Link

前提として本実装では、itemCategorycategory_nameがカテゴリ名で、それをメニューバーにし、同一ページ内のそれぞれのcategory_nameの部分に遷移させています。

実装

1. yarn add かnpmでインストール

Terminal
npm install --save react-router-hash-link

2.react-router-hash-linkMemoryRouter(後述するエラーが出たため)をimport

hoge.tsx
import {MemoryRouter} from 'react-router-dom';
import {HashLink} from 'react-router-hash-link';

3.遷移元となる横スクロールのメニューを作成

smoothをつけるだけでぬるっといい感じの遷移に

HashLinkの機能。とても便利。

href=#hogeになるようにid設定

JSX記法でto={'#' + itemCategory.category_name}とした。これで#hogeとなり、idに遷移できる。

謎のエラーをMemoryRouterで解決

HashLinkを使ったところ、Uncaught Error: Invariant failed: You should not use <Link> outside a <Router>のエラーが表示されたため、こちらの記事(解決方法: "Error: Invariant failed: You should not use outside a ")を参考に、MemoryRouterHashLinkを囲う。

ヘッダーの高さ調整

scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}でヘッダー部分の高さを考慮。この場合は160px分下げている。

hoge.tsx
<div className={classes.scrollMenuList}>
  <div className={classes.scrollMenuListMask}>
    {props.shopItemCategories.map((itemCategory) => (
      <Typography component="h2" variant="h3" >
    <MemoryRouter>
      <HashLink 
        smooth to={'#' + itemCategory.category_name} 
        scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}
        className={classes.scrollMenu}>{itemCategory.category_name}
      </HashLink>
    </MemoryRouter>
  </Typography>
  ))}
 </div>
</div>

4.スクロールバーを非表示に

こちらの記事(Appleに学ぶ、横スクロールナビを組む時のCSSメモ)を参考に、MaskとなるscrollMenuListMaskを用意し、親要素のscrollMenuListoverflow:"hidden"としラッパーする。各要素はアンダーバーがダサいので、textDecoration:"none"に。

hoge.tsx
   scrollMenuList: {
      width: '900px',
      overflow: "hidden",
      height: 60,
    },
    scrollMenuListMask: {
      overflowX: "auto",
      whiteSpace: "nowrap",
      display: 'flex',
      height: 80, //Maskのheightを親要素より高くすることがミソ
    },
    scrollMenu: {
      margin: theme.spacing(1, 2, 1),
      fontSize: 36,
      color: "#8f8f8f",
      textDecoration: "none"
    },

5.遷移先にid属性を振る

id属性を振るだけなので簡単。ここで振ったidを遷移元となるHashLinkで指定することでページ内遷移できる。

hoge.tsx
{props.shopItemCategories.map((itemCategory) => (
 <div className={classes.root}>
   <Typography component="h2" variant="h3" id={itemCategory.category_name}>
    {itemCategory.category_name}
   </Typography>
 </div>
...省略
))}

完成

完成したものが以下の通り。

hoge.tsx
//Import
import {MemoryRouter} from 'react-router-dom';
import {HashLink} from 'react-router-hash-link';

//該当部分のみのCSS
const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    scrollMenuList: {
      width: '900px',
      overflow: "hidden",
      height: 60,
    },
    scrollMenuListMask: {
      overflowX: "auto",
      whiteSpace: "nowrap",
      display: 'flex',
      height: 80,
    },
    scrollMenu: {
      margin: theme.spacing(1, 2, 1),
      fontSize: 36,
      color: "#8f8f8f",
      textDecoration: "none"
    },
  })
)

//スクロールスライドメニューとなる部分
<div className={classes.scrollMenuList}>
  <div className={classes.scrollMenuListMask}>
    {props.shopItemCategories.map((itemCategory) => (
      <Typography component="h2" variant="h3" >
    <MemoryRouter>
      <HashLink 
        smooth to={'#' + itemCategory.category_name} 
        scroll={el => { el.scrollIntoView(true); window.scrollBy(0, -160) }}
        className={classes.scrollMenu}>{itemCategory.category_name}
      </HashLink>
    </MemoryRouter>
  </Typography>
  ))}
 </div>
</div>

//メニューからの遷移先になる要素たち
{props.shopItemCategories.map((itemCategory) => (
<>
 <Box>
   <div className={classes.root}>
     <Typography component="h2" variant="h3" id={itemCategory.category_name}>
     {itemCategory.category_name}
   </Typography>
  </div>
 </Box>

...省略
</>
))}

今回も色々な記事を参考にさせていただきました。横スクロールのスライダーみたいなのは結構使いたい場面多いと思うので、少しでも参考になれば嬉しいです。

参考

横スクロールナビゲーションを実装する3つの方法
Reactでページ内リンクを実装する
【React Router】画面遷移時に#を用いて特定の要素に移動させる方法
Reactで動的に属性の値を生成する方法

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

Rails 6.1・React・Docker・MySQLで環境構築

前書き

2021年3月からE2Eテストの自動化を担当し、初めて仕事でコードを書いている。
テスト対象のアプリケーションのapiがRails、フロントがReactで作られているので、
興味本意でプライベートで何か作ってみることにした。
(職場でテストコードをJSで書くので、JSに慣れるという意味合いもある。)

早速、Dockerで環境構築してみたので、備忘として残す。

環境

・macOS Big Sur 11.2.3
・Ruby 3.0
・Rails 6.1.3.1
・Docker 20.10.5
・docker-compose 1.28.5
・Mysql 8.0

手順

全体をざっくりと説明すると以下の通り。

1.ファイルを全て準備する
2.docker-compose buildする
3.railsとreactの各種コマンド実行
4.docker-compose upする

1.ファイルを全て準備する

最終的に、以下のような構造になる。

spa-chat
|-- docker-compose.yml
|-- api
    |-- entrypoint.sh
    |-- Gemfile
    |-- Gemfile.lock
    |-- Dockerfile
|-- front
    |-- Dockerfile

Rails関連

Gemfile

Gemfile
source 'https://rubygems.org'
gem 'rails', '~>6'

Gemfileには、使いたいRailsのバージョンを記入。
この場合、Rails6系の最新のバージョンを採用。

Gemfile.lock

touch Gemfile.lock

空のGemfile.lockを作成。

entrypoint.sh

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

Dockerfile

Dockerfile
FROM ruby:3.0
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
  && apt-get update -qq \
  && apt-get install -y nodejs yarn \
  && mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

Dockerfileについては、Rails5系とRails6系で書き方が異なるので注意が必要。
Rails6系では、JavascriptコンパイラがWebpackerになったことに起因する。

React関連

Dockerfile

Dockerfile
FROM node:15.13.0-alpine
RUN mkdir /myapp
WORKDIR /myapp

docker-compose.yml

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  api:
    build: ./api
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - ./api:/myapp
      - gem_data:/usr/local/bundle
    ports:
      - "3000:3000"
    depends_on:
      - db
    stdin_open: true
    tty: true
  front:
    build: ./front
    command: yarn start
    ports:
      - '8000:3000'
    volumes:
      - ./front:/myapp
    depends_on:
      - api

volumes:
  mysql-data:
  gem_data:
    driver: local

2.docker-compose buildする

各種ファイルを揃えたので、イメージをbuild。

docker-compose build

3.RailsとReactの各種コマンド実行

コマンドを実行し、必要なファイルを揃えていく。

React


ローカルで立ち上げる時と同じような感じで、以下のコマンドを実行。
docker-compose run front npx create-react-app front

※原因は突き止められなかったが、フロント用のDockerfileを置いたfrontディレクトリに、
さらに別のfrontディレクトリが作成され、その配下に各種フォルダが作成された。
なので、Dockerfileと同じ階層に、先程のコマンドで作成したフォルダを移動する必要がある。
(移動させないと、ブラウザからフロントにアクセスできない。)

アドバイスいただけますと幸いです。

Rails

まず、以下のコマンドを実行。

docker-compose run api rails new . --force --no-deps --database=mysql --skip-test --webpacker --api

apiモードでrails new。
RSpecでテストコードを書くので、testディレクトリは作成しない。

次に、config/database.ymlを編集。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("MYSQL_USERNAME", "root") %>
  password: <%= ENV.fetch("MYSQL_PASSWORD", "password") %>
  host: <%= ENV.fetch("MYSQL_HOST", "db") %>

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

そして、データベースを作成。

docker-compose run api rake db:create

4.docker-compose upする

準備が整ったので、コンテナを起動。

docker-compose up -d

localhost:8000で見慣れたReactの画面が表示。
image.png

localhost:3000で見慣れたRailsの画面が表示。
image.png

参考にしたサイト

https://qiita.com/nsy_13/items/9fbc929f173984c30b5d
Rails側の設定を書くときにとても参考になりました。

https://nakatanorihito.com/programming/docker-rails-api-react-postgresql/
React側の記述や、ディレクトリ構造を参考にしました。
docker-compose.ymlは上記2サイトをどちらも参考にしています。

https://docs.docker.com/compose/rails/
docker docsに記載がある、Railsの環境構築方法。
Rails5系を使うことを前提に書かれている。

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

ReactでonClickで指定したイベントハンドラがレンダー時に発火する

環境

  • react 17.0.1

内容

Material-UIのchipのタイトルをイベントハンドラの引数に指定したら、レンダー時に発火した。

レンダー時に発火するコード
export default function TagsSerch() {
    const handleClick = (title) => {
        console.info(title);
    };

    /* 色々省略 */

    return (
        <Box>
            <Typography variant="subtitle1">タグ検索</Typography>
            {tagList.map((tag, index) => (
                <Chip
                className={classes.tags}
                name={tag.title}
                label={tag.title}
                clickable
                color="primary"
                size="small"
                onClick={handleClick(tag.title)}    /* ⇦ここ */
                key={tag.title}
                />
            ))}
        </Box>

    );
}

React公式サイトによると、こんな感じで引数を渡してくださいとのこと。

レンダー時に発火せず引数をイベントハンドラに渡す
export default function TagsSerch() {
    const handleClick = (title, e) => {
        console.info(title);
        console.info(e);
    };

    /* 色々省略 */

    return (
        <Box>
            <Typography variant="subtitle1">タグ検索</Typography>
            {tagList.map((tag, index) => (
                <Chip
                className={classes.tags}
                name={tag.title}
                label={tag.title}
                clickable
                color="primary"
                size="small"
                onClick={onClick={(e) => handleClick(tag.title, e)}}    /* アロー関数かまして呼び出すよ */
                key={tag.title}
                />
            ))}
        </Box>

    );
}

今回はイベントオブジェクトはいらないので、最終的にこんな感じになりました。

レンダー時に発火せず引数をイベントハンドラに渡す(イベントオブジェクトなし)
export default function TagsSerch() {
    const handleClick = (title) => {
        console.info(title);
    };

    /* 色々省略 */

    return (
        <Box>
            <Typography variant="subtitle1">タグ検索</Typography>
            {tagList.map((tag, index) => (
                <Chip
                className={classes.tags}
                name={tag.title}
                label={tag.title}
                clickable
                color="primary"
                size="small"
                onClick={onClick={() => handleClick(tag.title)}}    /* アロー関数かまして呼び出すよ */
                key={tag.title}
                />
            ))}
        </Box>

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

webpack + babel + TypeScript + React メモ

webpack + babel + TypeScript + React 環境構築メモ

  • init
    • npm init -y
  • Babel
    • npm i -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
    • .babelrc か babel.config.json を作成する(.babelrcはディレクトリ単位、babel.config.jsonはプロジェクト全体)
  • webpack
    • npm i -D webpack webpack-cli babel-loader ts-loader
    • webpack.config.js
  • React、TypeScript
    • npm i -S react react-dom
    • npm i -D typescript @types/react @types/react-dom
    • npx tsc init
    • src/index.ts
  • webpack-dev-server
    • npm i -D webpack-dev-server
    • dist/index.html
    • package.json
    • npm start

コマンドの説明

  • npm init -y ・・・ package.json をデフォルト設定で作成
  • npm i -S = npm install --save ・・・ package.json の dependenciesに追加
  • npm i -D = npm install --save-dev ・・・ package.json の devDependenciesに追加

babel

.babelrc
{
  "presets": [
    "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"
  ]
}

webpack

webpack.config.js
module.exports = {
    // 本番にデプロイするときはモード値を production に設定
    // development に設定すると元のファイルとの関連性がわかるソースマップと一緒に出力される
    mode: 'development',

    // 環境によってはsrc/index.ts
    entry: 'src/index.tsx',

    module: {
        rules: [
            {
                test: /\.tsx?$/,    // ? で、.ts or .tsx
                use: 'ts-loader', // TypeScript用のloader
            },
        ],
    },
    resolve: {
        // React × TypeScriptで使う可能性のある拡張子を全て記述
        extensions: [
            '.ts', '.js', '.tsx', '.jsx'
        ],
    },
};

React + TypeScript

src/index.tsx
import React from 'react';
import { render } from 'react-dom'

render(
  <h1>React x TypeScript!</h1>,
  document.getElementById('root') 
)
dist/index.html
<!DOCTYPE html>
<html>
<head>
  <title>Hello, React!</title>
</head>
<body>
  <div id="root"></div>
  <script src="./bundle.js"></script>
</body>
</html>

webpack-dev-server

package.json
{
  // 省略
  "scripts": {
    "start": "webpack serve",  // 追加
  },
  // 省略
}
npm start

参考
https://tech.playground.style/javascript/babel-webpack/
https://tech.playground.style/javascript/babel-webpack-build/

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

Reactで複数要素に動的にクリック→スクロールする(createRef, useRef)

Reactでクリック→スクロールする方法について

Reactで『クリック→該当部分までスクロール』を行う場合、以下のようにcreateRefを使用することがありました。

App.tsx
const ref = createRef<HTMLDivElement>()

ここで定義したrefをスクロールさせたい箇所に渡して、

App.tsx
const handleJump = useCallback(() => {
  ref!.current!.scrollIntoView({ behavior: "smooth" })
}, [ref])

クリック時にスクロールするよう関数を定義する。すると以下のようにクリック→スクロールができます。

sample.gif

ソースコードは以下を参照いただければ幸いです。
CodeSandbox

ですが、これだとスクロール箇所が増える度に、ユニークなrefを定義していく必要があるので、
複数の要素に動的にスクロールさせることを検討しました。

結論

以下がソースコードです。
CodeSandbox

App.tsx
type Item = {
  title: string;
  background: string;
  service: string;
  otherContent?: boolean;
};

const items: Item[] = [
  { title: "コンテンツ1", background: "skyblue", service: "サービス1" },
  { title: "コンテンツ2", background: "yellow", service: "サービス2" },
  { title: "コンテンツ3", background: "green", service: "サービス3", otherContent: true }
];

↑のような配列のデータがある場合、

App.tsx
  const pageRef = useRef(items.map(() => createRef<HTMLDivElement>()));

  const scrollToView = (id: number) => {
    pageRef.current[id]!.current!.scrollIntoView({ behavior: "smooth" });
  };

↑のコードのように
- mapを使ってrefの配列を作り
- 関数(scrollToView)の引数にはidを受け取る

とすることでrefを何度も定義せずに動的にクリック→スクロールをさせることができました!

wiita.gif

参考

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

GitHub Actionsの基礎③ CI/CDパイプラインの構築

GitHub Actions学習のまとめとして、Create React AppのプロジェクトのCI/CDパイプラインを構築してみました。

使用したnpmパッケージについては以下の記事でまとめています。

プロジェクトの作成

npx create-react-app react-app --use-npmでプロジェクトを作成します。

ワークフローの構成

Git-flowでの運用を想定して、以下のタイミングで実行するワークフローを作成します。

  1. featureからdevelop向けのプルリクを作成したとき
  2. プルリクが承認されてfeatureがdevelopへマージされたとき
  3. developからmaster向けのプルリクを作成したとき
  4. プルリクが承認されてdevelopがmasterへマージされたとき

スクリーンショット 2021-04-02 22.05.56.png

スクリーンショット 2021-04-02 22.12.45.png

加えて、リリースされたらSlackに通知するワークフローも作成します。

ワークフローの作成

前項の図でワークフローが4つありましたが、これらを1つのymlファイルに記述します。

ワークフロー①の作成(一部分)

まずは①(featureからdevelop向けのプルリクを作成したとき)のワークフローの内容を記述します。

ci.yml
name: CI
on:
  pull_request:
    branches: [develop] #develop向けのプルリクが作成されたときにジョブを実行

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2 #チェックアウトするアクション
      - name: Use NodeJS
        uses: actions/setup-node@v1 #特定バージョンのnodeを設定するアクション
        with:
          node-version: "12.x"
      - run: npm ci #dependencyのインストール
      - run: npm run format:check #コードフォーマットチェック
      - run: npm test -- --coverage #テスト実行
        env:
          CI: true #現在の環境がCI環境であることを仮定

ここではdependencyのインストール、コードフォーマットチェック、テスト実行を記述しています。
CIにおいては、npm installではなくnpm ciでdependencyのインストールを行います。npm ciでは、依存関係の更新をせずに整合性チェックと依存パッケージのダウンロードのみを行うため、npm installより高速に動作し、CIで必要なことだけを行うことができます。

CI: trueではテストがCI環境で実行されることを仮定しています。

ワークフロー②の作成

developからworkflowというfeatureブランチをきって、git pushしてdevelop向けのプルリクをつくるとワークフローが実行されます。
スクリーンショット 2021-03-28 9.49.35.png

ワークフロー②(プルリクが承認されてfeatureがdevelopへマージされたとき)の内容のステップを追記します。

ci.yml
name: CI
on:
  pull_request:
    branches: [develop]
  push:
    branches: [develop] #developにマージされたときに実行

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use NodeJS
        uses: actions/setup-node@v1
        with:
          node-version: "12.x"
      - run: npm ci
      - run: npm run format:check
      - run: npm test -- --coverage
        env:
          CI: true
      - name: Build Project #ステージング用のビルドを行うステップ
        if: github.event_name == 'push' #pushイベントのときだけ実行
        run: npm run build #ビルド実行
      - run: npm install -g surge #surgeのインストール
      - name: Deploy to Staging #ステージングデプロイを行うステップ
        if: github.event_name == 'push' #pushイベントのときだけ実行
        run: npx surge --project ./build --domain awesome-quiet.surge.sh #デプロイ
        env: #surgeの認証
          SURGE_LOGIN: ${{ secrets.SURGE_LOGIN }} #surge whoamiで取得
          SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} #surge tokenで取得

developへのマージをトリガにするために、develop向けのpushイベントを追加します。
また、ワークフローは①と②で共有されているので、ビルドやデプロイがpull_requestイベントで実行されないように、if: github.event_name == 'push'という実行条件を追加します。

今回、デプロイにはsurgeというnpmパッケージを使用しています。ログイン名とパスワード(token)を予め作成して、GitHubリポジトリのsecretsに環境変数として登録しておきます。

ワークフローの実行内容を確認すると、developへのプルリク時点ではbuild, deployステップを飛ばしています。
スクリーンショット 2021-03-28 10.33.37.png

developへマージ(push)するとbuild, deployが実行されています。
スクリーンショット 2021-03-28 10.49.50.png

dependencyのキャッシュ

ワークフローごとにnpm ci行うのは無駄なので、dependenciesのキャッシュ用のアクションをステップに追加します。

    steps:
      - uses: actions/checkout@v2
      - name: Cache node_modules
        uses: actions/cache@v1
        with:
          path: ~/.npm #キャッシュしたファイルとキーを格納する場所(OSによって異なる)
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} #キャッシュのリストアや保存をするためのキー
          restore-keys: | #キャッシュをリストアするためのキーのリスト
            ${{ runner.os }}-node-

hashFilesを使用することで、depencencyに変更があったときに新しいキャッシュをつくることができます。

最初のワークフローを実行すると、以下のようにキーが生成されます。
スクリーンショット 2021-03-28 21.11.23.png

Artifact(テストカバレッジ、ビルドファイル)のアップロード

テストとビルドの後にGitHubのActionsタブからArtifactをダウンロードできるようにするため、これらをアップロードするアクションをステップを追加します。

ci.yml
      - run: npm test -- --coverage
        env:
          CI: true
      - name: Upload Test Coverage
        uses: actions/upload-artifact@v1 #artifactをアップロードするアクション
        with:
          name: code-coverage #ダウンロード時の表示名
          path: coverage #アップロードするフォルダのパス
      - name: Build Project
        if: github.event_name == 'push'
        run: npm run build
      - name: Upload Build Coverage
        if: github.event_name == 'push'
        uses: actions/upload-artifact@v1
        with:
          name: build
          path: build

ワークフローを実行すると以下のようにArtifactがアップロードされます。
スクリーンショット 2021-03-28 21.55.09.png

masterにマージされたときだけ実行するステップ

ワークフロー④(プルリクが承認されてdevelopがmasterへマージされたとき)のときだけ実行されるステップを作成します。

リリースの作成

masterにマージされたときにリリースを作成するステップを追加します。
リリースの作成にはsemantic-releaseを使います。

ci.yml
      - name: Create a Release
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: npx semanic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

また、リリースの画面からbuild.zip, coverage.zipをダウンロードできるようにします。

ci.yml
      - name: ZIP Assets
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: |
          zip -r build.zip ./build
          zip -r coverage.zip ./coverage

リリースの設定は以下のようになります。

release.config.js
module.exports = {
  branches: "master",
  repositoryUrl: "https://github.com/suzuki0430/react-app",
  plugins: [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    [
      "@semantic-release/github",
      {
        assets: [
          {path: "build.zip", label: "Build"},
          {path: "coverage.zip", label: "Coverage"},
        ],
      },
    ],
  ],
};

codecovへのテストカバレッジのアップロード

codecovはコードのカバレッジを計測し、可視化したりslackに通知したりできる外部サービスです。
envでcodecovで作成したTOKENを指定するだけで、カバレッジレポートをアップロードすることができます。

ci.yml
      - name: Upload Coverage Reports
        if: github.event_name == 'push' && github.ref == 'refs/heads/master'
        run: npx code-coverage
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Slack通知用のワークフローの作成

リリースが作成されたときにSlack通知を行うワークフローを作成します。
ci.ymlのCreate releaseステップをトリガにするため、CUSTOM_TOKENを新しくつくります。

スクリーンショット 2021-03-29 21.27.59.png

リリースの作成(published)をトリガにしたワークフローを以下のように作成します。

release.yml
name: Notify on Release
on:
  release:
    types: [published]

jobs:
  slack-message:
    runs-on: ubuntu-latest
    steps:
      - name: Slack Message
        run: |
          curl -X POST -H 'Content-type: application/json' --data '{"text":"New release ${{ github.event.release.tag_name }} is out, <${{ github.event.release.html_url }}|check it out now.>"}' ${{ secrets.SLACK_WEBHOOK }}

おわりに

GitHub Actionsの使い方が大体わかったので、今度は社内業務で役に立つようなワークフローを作成してみたいと思います。

参考資料

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