20200924のReactに関する記事は6件です。

Next.jsを ver8.1.0からver10.0.3に上げたログ

この記事はLivesense アドベントカレンダー2020の20日目の記事です。
今期、プロダクトで利用しているNext.jsのバージョンをv8.1.0からv10.0.3まで上げたので、マイナーバージョンごとのアップグレード時の作業内容を公開しようと思います。
今後同じような作業をする方の参考になると嬉しいです。

転職会議チームはページが取り扱うドメインごとにリポジトリが分かれていて、2020年12月現在Next.jsを利用しているページの例が以下です。

企業検索ページ: https://jobtalk.jp/companies/search
企業詳細ページ: https://jobtalk.jp/companies/4075, https://jobtalk.jp/companies/4075/answers など

これらのページのフロントエンドアプリケーションがNext.jsで作成されていて、データ取得時にRails製のBFFおよびAPIサーバーと通信する、というような構成です。利用している主なライブラリのバージョンは以下の通りです。

"react": "^16.13.1",
"redux": "^4.0.5",
"redux-observable": "^1.2.0",
"typescript": "^3.8.3",

アップグレードは以下のような方針で進めました。

  • マイナーバージョンごとのアップグレードを行い都度打鍵
  • マイナーバージョンアップグレード時のパッチバージョンはその時点で最新のものを適用
  • 大幅な修正が入る対応などについてはマイナーバージョンアップグレードとは分けて対応

8.1.0 -> 9.0.8

ver 9 公式ブログ
ver 9.0.7 公式ブログ

Next.jsおよび関連ライブラリのバージョンアップグレード

package.json
{
  ...
  "dependencies": {
    ...
-    "next": "^8.1.0",
+    "next": "9.0.8",
-    "next-redux-wrapper": "^3.0.0",
+    "next-redux-wrapper": "^4.0.1", // Next.js ver9への対応
-    "@zeit/next-typescript": "^1.1.1",
  },
  "devDependencies": {
-    "@types/next": "^8.0.5", // ビルトインTSサポートにより不要化
-    "@types/next-redux-wrapper": "^2.0.2", // ビルトインTSサポートにより不要化
-    "fork-ts-checker-webpack-plugin": "^4.1.3", // ビルトインTSサポートにより不要化
  },
  ...
}

NextFC廃止 => NextPageへ

src/pages/companyDetail.tsx
import { NextPage } from 'next'

// 型をちゃんとしてないのは一旦後回し
- const CompanyPage: NextFC<PropsType, { companyId: number; isServer?: boolean }, any> = ({
+ const CompanyPage: NextPage<any> = ({
...

next/linkの外部リンクとしての使用法の廃止

ver8系以前はリポジトリ外へのリンクにnext/linkのLinkコンポーネントを利用できていましたが、9系以降は外部リンクにLinkコンポーネントを利用すると、Invalid href passed to routerというエラーが吐かれるようになりました。
(表示されるエラー: https://github.com/vercel/next.js/blob/master/errors/invalid-href-passed.md)
これを受けて、外部リンクについてはaタグを利用するように修正しました。

const LinkToGoogle: React.FC = () => (
- <Link href={'https://google.com'}>
-   <a>Google</a>
- </Link>
+ <a href="https://google.com">Google</a>
)

useRouterのジェネリクス経由でのrouter.queryの型指定を廃止

ver8系以前はnext/routerからインポートしたuseRouterに対して、ジェネリクス経由でrouter.queryの型を指定する方法が利用できました。
たとえば

import { useRouter } from 'next/router'

const QueryText: React.FC = () => {
  const query = useRouter<QueryType>().query
  return (
    <p>query.text</p>
  )
}

みたいな使い方です。
このジェネリクス経由でのクエリ型指定が廃止され、返却されるqueryの型はParsedUrlQueryという型で統一されることになりました。
型の内容は string | string[] | undefined なのでさもありなんという感じですが、「数値の文字列であることを保証したい」などの場合には型ガードなどで対応する必要がありそうです。

型ガードでクエリの型の保証をするしないに関わらず、今回のver9化ではジェネリクス経由での型指定を削除する必要があります。

-  const query = useRouter<QueryType>().query
+  const query = useRouter().query

dynamic importのloadingオプションの初期値変更に伴う修正

Next.jsのdynamic importを利用している場合、コンポーネントのロードが完了するまでの間に表示されるコンポーネントを示すloadingオプションの初期値が、() => nullに変更されました。
確か以前は() => loading...とかだった気がしますが、これを回避するためにオプションで loading: () => nullと指定していた箇所については、オプションを削除することが可能になります。

import dynamic from 'next/dynamic'

const DynamicImported = dynamic(
  () => import('src/component/DynamicImported').then(mod => mod.DynamicImported),
- { loading: () => null }
)

WithRouterPropsのimport先の変更

next/routerからインポートするwithRouterでラップしたコンポーネントに渡るPropsの型定義ですが、こちらの型定義がnext/routerからnext/dist/client/with-routerに変更されたため、インポート先を修正する必要があります。

- import { withRouter, WithRouterProps } from 'next/router'
+ import { withRouter } from 'next/router'
+ import { WithRouterProps } from 'next/dist/client/with-router'

_app.tsxにおけるContainerコンポーネント利用の廃止

表題の通りです。以下のように修正します

src/pages/_app.tsx
import React from 'react'
import App, { Container } from 'next/app'

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props
-   return (
-     <Container>
-       <Component {...pageProps} />
-     </Container>
-   )
+   return <Component {...pageProps} />
  }
}

export default MyApp

参照: https://github.com/vercel/next.js/blob/master/errors/app-container-deprecated.md

viewPortを設定するmetaタグをpages/_documentからpages/_appコンポーネントに移行

以下のエラーそのままです。
https://github.com/vercel/next.js/blob/master/errors/no-document-viewport-meta.md

Next.js ver9の型定義に則り、Pagesコンポーネントの型を整備

Next.js ver9系のPagesコンポーネントの型定義は、概ね以下のような感じで定義できます。

import * as React from 'react'
import { NextPage, NextPageContext } from 'next'
import { connect } from 'react-redux'
import { ParsedUrlQuery } from 'querystring'


const TestPage: NextPage<PropsType, InitialPropsType> = ({
  stateItem,
  query,
  fromAppOrDocumentItem,
  initialProps
}) => (
  <div>
    {stateItem}
    {query}
    {fromAppOrDocumentItem}
    {initialProps}
  </div>
)

TestPage.getInitialProps = async ({ query }: NextPageContext) => {
  const initialProps = !!query
  return {query, initialProps}
}

type FromAppOrDocumentItem = {
  fromAppOrDocumentItem: boolean
}

type PropsType = ReturnType<typeof mapStateToProps> & FromAppOrDocumentItem & InitialPropsType

type InitialPropsType = {
  query: ParsedUrlQuery
  initialProps: boolean
}

type StatesType = {
  stateItem: string
}

const mapStateToProps = ({ stateItem }: StatesType) => ({ stateItem })

export default connect(mapStateToProps)(TestPage)

// propsの流れるイメージ図
/**
 * <App>/<Document>
 *   ↓
 * (connect Store)
 *   ↓
 *   ↓ → → → → → → → → → → → →
 *   ↓                        ↓
 *   ↓                 getInitialProps
 *   ↓                        ↓
 *   ↓ ← ← ← ← ← ← ← ← ← ← ← ←
 *   ↓
 * NextPage
 */

これに則り、既存のPagesコンポーネントの型を整備してゆきました。

DynamicRouting対応

例えばver8.1の頃は

- src/
    - pages/
        - companySearch.tsx
        - companyDetail.tsx
        - companyAnswers.tsx
        - companyAnswerDetail.tsx
        - companyJobs.tsx
        - companyOccupations.tsx
        - companyOccupationDetail.tsx

のように、ページの概要を表現した名前のファイルが同階層に並ぶpagesディレクトリがあり、加えて src/server.jsに定義するカスタムサーバーにて

src/server.js
  server.get('/companies/:id', (req, res) => {
    const id = req.params.id
    if (isValidCompanyId(id)) {
      app.render(req, res, `/companyDetail`, { companyId: id })
    } else {
      res.status(404).end()
    }
  })

のように、リクエスト先のURLを見て各pagesコンポーネントにリクエストを振り分ける、というような実装をしていました。
これを、DynamicRoutingを導入することで

- src/
    - pages/
        - companies/
            - [companyId].tsx
            - [companyId]/
                - answers.tsx
                - answers/
                    - [answerId].tsx
                - occupations.tsx
                - occupations/
                    - [occupationId].tsx
            - search.tsx

というように、URLの構造と対応するディレクトリ構造のPagesコンポーネントファイルを配置すれば、[]で囲ったパスに該当するリクエスト先URLのパスをNext.jsが解析し、Pagesコンポーネント側で router.queryからパスパラメータを引けるようになります。
こういった修正を通して、リクエストのPagesコンポーネントへの振り分けを開発スコープから外せる点を期待して、DynamicRoutingを導入することにしました。

ファイル名の変更

DynamicRoutingに対応するページをURLに対応する形にrenameします

$ mkdir src/pages/companies
$ mv src/pages/companyDetail.tsx src/pages/companies/[companyId].tsx

カスタムサーバーのパス振り分け処理の削除

カスタムサーバーからDynamicRoutingに対応するパスへの振り分け処理を削除

src/server.js
- server.get('/companies/:id', (req, res) => {
-   const id = req.params.id
-   if (isValidCompanyId(id)) {
-     app.render(req, res, `/companyDetail`, { companyId: id })
-   } else {
-     res.status(404).end()
-   }
- })

Linkコンポーネントのhrefプロパティの修正

DynamicRoutingの利用に伴い、Linkコンポーネントのプロパティの渡し方が変わります。

import Link from 'next/link'

export const CompanyDetailLink: React.FC<{companyId: number}> = ({companyId}) => {
-  const href = `/companyDetail?companyId=${companyId}`
+  const href = '/companies/[companyId]' // pagesコンポーネントファイルのファイル名を指定
  const as = `/companies/${companyId}` // パスパラメータを含んだパスを指定

  return <Link href={href} as={as}><a>link</a></Link>
}

9.0.8 -> 9.1.7

ver 9.1 公式ブログ
ver 9.1.7 公式ブログ

Next.jsのバージョンアップグレード

package.json
  "dependencies": {
    ...
-   "next": "9.0.8",
+   "next": "9.1.7",
    ...
  },

9.1.7 -> 9.3.6

ver 9.2 公式アップグレードブログ
ver 9.3 公式アップグレードブログ

※ 一度ver9.2系最新(当時)の9.2.2に上げたのですが、IE11でポリフィルが適用されないバグがあり、ver9.3系では対応されているとのことだったので、9.1.7から9.3.6に上げることにしました。

built-in CSS Support対応

転職会議の当リポジトリでは@zeit/next-cssを利用してcssのインポートを行なっていましたが、ビルトインのCSS(モジュール)のサポートが追加されたことにより、next-cssを消すことができました。

ビルトインCSSサポートに乗ると、global CSSのimportをできる箇所はpages/_app.tsxに限定されます。
なので、他の箇所でglobal CSSをimportしている場合は、pages/_app.tsxに集約する必要があります。

moduled CSSのファイル名の修正

cssモジュール用の設定が不要になる一方で、Next.jsのビルトインCSSサポート上でcss moduleを利用しようとすると、ファイル名を{任意のファイル名}.module.cssという形式にする必要があります。
ワンライナースクリプト等で一括変更できればよかったのですが、パッとわからなかったので330ファイルほどを手作業でrenameしました。

next.config.jsからCSSモジュール用の設定を削除

next.config.js
- const withCss = require('@zeit/next-css')
...

- const cssConfig = [
-   withCss,
-   {
-     cssModules: true,
-     cssLoaderOptions: {
-       importLoaders: 1,
-       localIdentName: '[local]___[hash:base64:5]'
-     }
-   }
- ]
...

module.exports = withPlugins(
- [cssConfig, withOptimizedImages],
+ [withOptimizedImages],
  nextConfig
)

Next.jsのバージョンアップグレードおよびnext-cssへの依存削除

package.json
  "dependencies": {
    ...
-   "@zeit/next-css": "^1.0.1",
-   "next": "9.1.7",
+   "next": "9.3.6",
    ...
  },

9.3.6 -> 9.4.4

ver 9.4 公式ブログ

Next.jsのバージョンアップグレード

package.json
  "dependencies": {
    ...
-   "next": "9.3.6",
+   "next": "9.4.4",
    ...
  },

9.4.4 -> 9.5.5

ver 9.5 公式ブログ

next/linkのasプロパティの利用方法変更への対応

ver9.5.3からhrefに指定したパスを解析してpages/配下のファイルパスに当てはめてくれるようになったので、hrefに明示的にファイルパスを渡す必要がなくなりました。ただこの変更は後方互換性があるので、対応しなくてもバージョンを上げることは可能です。

// pages/companies/[companyId].tsx というファイルがある場合
- <Link href={"/companies/[companyId]"} as={"/companies/4075"}>
+ <Link href={"/companies/4075"}>
    <a>リブセンス</a>
  </Link>

外部ドメインのCDNからアセットを取得する際に、arrow-originの設定がされていてもCORSエラーになるバグへの対応

報告されているissue にあるコメント通り、next.config.jsにcrossOriginプロパティを追加します。

next.config.js
const nextConfig = {
  ...
  crossOrigin: 'anonymous',
  ...
}

Next.jsのバージョンアップグレード

package.json
  "dependencies": {
    ...
-   "next": "9.4.4",
+   "next": "9.5.5",
    ...
  },

9.5.5 -> 10.0.3

ver 10 公式ブログ

Next.jsのバージョンアップグレード

package.json
  "dependencies": {
    ...
-   "next": "9.5.5",
+   "next": "10.0.3",
    ...
  },

おわりに

以上でNext.jsのバージョンアップグレードは完了です。
next/imageやgetInitialProps -> getServerSidePropsへの移行、部分的なSSG、React17への対応など、まだ利用しきれていない機能も多いので、これからいろいろ試してみたいと思います。

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

【Gatsby.js】単一のjsonファイルを元に複数のページを生成する

Gatsby.jsを使って、jsonファイルから複数のページを生成する方法をまとめます。
Gatsby、情報自体はそこそこ豊富なんですが、日本語の情報が少ないですね。。

記事データをまとめたjsonファイルを作成

$ mkdir src/data
$ touch src/data/articles.json
src/data/articles.json
[
  {
    "slug": "how-to-use-gatsby",
    "title": "【Gatsby.js】「ここだけ押さえれば普通に使える」って知識をまとめてみた",
    "content": "Gatsby.jsでシンプルな静的サイトを作る際に必要な知識だけをまとめました。CMSとの連携等については今回は扱いません。本文テキスト本文テキスト本文テキスト本文テキスト本文テキスト本文テキスト",
    "tags": ["React", "Gatsby"]
  },
  {
    "slug": "learn-react",
    "title": "Reactの学習、今からやるならこうする",
    "content": "Reactの学習を初めて約2週間が経ちました。Railsの時と違って「とりあえずこれをやっておけば良い」ってものが見当たらずなかなか大変でしたが、試行錯誤の上色々と見えてきたのでまとめていきます。",
    "tags": ["React"]
  },
  {
    "slug": "restore-mac",
    "title": "Macの初期化→リストアを良い感じにする",
    "content": "Brewfile、Mackup、Docker、Dropboxを使って良い感じにリストアできる環境を作ったので、まとめます。以下の記事をめちゃくちゃ参考にしました。macOSでの開発環境を全部Docker化したらリストア時間が1時間半になった",
    "tags": ["Docker", "Brewfile", "Mackup"]
  }
]

記事のテンプレートページのベースを作成

$ mkdir src/templates
$ touch src/templates/article.js
src/templates/article.js
import React from "react"

export default () => (
  <>
    <h1>タイトル</h1>
    <p>本文</p>
    <span>タグ1</span>
    <span>タグ2</span>
  </>
)

記事を生成する処理を記述

$ touch gatsby-node.js
gatsby-node.js
const path = require("path")
const data = require("./src/data/articles.json")

exports.createPages = ({ actions }) => {
  const { createPage } = actions
  const template = path.resolve("./src/templates/article.js")
  data.forEach(article_object => {
    const path = `articles/${article_object.slug}`
    createPage({
      path, // 生成されるページのpath
      component: template, // ページのベースとなるテンプレート 
      context: article_object, // GraphQL経由で受け渡すデータのオブジェクト
    })
  })
}

GraphiQLを開き、データを取得するためのクエリを作成

gatsby developで開発サーバーを立ち上げ、ブラウザで http://localhost:8000/ にアクセスしてGraphiQLを開く。
allSitePage > edges > node > contextと順に開いていくと、titlecontenttagsが見つかるのでチェックを入れて実行し、欲しいデータが取得できることを確認。

GraphQLで取得したデータをテンプレートページに埋め込む

作成したクエリで必要なデータを取得し、テンプレートに埋め込みます。
GraphQLで取得したデータはdataというオブジェクトに格納されるので、そこから値を取り出して出力します。

src/templates/article.js
import { graphql } from "gatsby"
import React from "react"

export default ({ data }) => {
  const article = data.allSitePage.edges[0].node.context
  return (
    <>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
      {article.tags.map(tag => (
        <span>{tag}</span>
      ))}
    </>
  )
}

export const query = graphql`
  query($path: String!) {
    allSitePage(filter: { path: { eq: $path } }) {
      edges {
        node {
          context {
            content
            tags
            title
          }
        }
      }
    }
  }
`

開発サーバーを再起動して確認

開発サーバーを再起動し、存在しないURLを入力して404ページを表示すると、ページ一覧に今作ったページが追加されていることが確認できます。

参考

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

[React] 初心者は必見でしょ。ルーティングのエラー解決と解説

どうもこんにちわ

今回は、Reactのルーティングを設定する上でよく目にする

Error: Invariant failed: You should not use <Link> outside a <Router>

というエラーについて、僕自身調べてみて

Typescript中心のエラーの解決法が多いや、、。

と思ったので、シンプルなReactでの説明をしていきたいと思います。
また、解決に至るまでのプロセスで出会った3つの関数?(っていうのかな)たちも
どんどん解説していくので、ぜひ最後まで見ていってください。

まず僕がこのエラーに引っかかった時のコードかこちら、

App.js
import React from "react";
import { Link } from "react-router-dom";
export default function Home() {

  return (
    <div className="container">
      <title>Hands on Mania</title>
      <main className="u-text-center">
        <h1 className="font-family-homemade">Hands on Mania</h1>
        <p className="description">studying</p>  
        <Link to="/Auth/SignIn">signin</Link>
      </main>
    </div>
  );
}

策①   Memoryrouterを使う

最初は、react-router-domから「Memoryrouter」をインポートして

        <Memoryrouter>
          <Link to="/Auth/SignIn">signin</Link>
        </Memoryrouter>

このようにしましょう。との解決策でした

早速試したのですが、、結果は変わらず。。

まぁまぁ焦らず、ゆっくり行こーや。
ここで解決できてたらQiitaの記事にはしてません。

では簡単に解説します。

Memoryrouterについて簡単に調べたところ、

テストおよびReact Nativeのような非ブラウザー環境で役立ちます。

と調べて一番上にあった記事で解説されていたので、
テストコードを書く時。または、ReactNative環境で普通は使うものなのかな?
と思いました。

Memoryrouterと似たような(?)ものにhooksのuseMemoが思い出されたので、
こちらも申し訳程度に紹介。

React公式によれば

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

このような公式で使い、あくまで最適化のために使うそうだ
初心者にはまだ早い領域かもしれないですね?

策② Route と Switch を使う

こちらはgithubに乗っていた解決策だったので、
これはいけるっしょ。と内心ヨユーな表情で思っていたのですが、
あっけなく予想を裏切られました。
(僕のやり方が違う可能性が高い、、。)

方法としては、このように記述するのさっ。というやり方です。

import { Route, Switch } from "react-router";


   <Switch>
      <Route exact path="/Auth/SignIn" />
   </Switch>

しかし、結果は儚くとも同じようなエラー内容。。

Error: Invariant failed: You should not use <Switch> outside a <Router>

だめだー、、。
俺の人生終わったー、、。

諦めないで!
そうなるのはまだ早い。

次で解決策を提示しながら一緒に解説するから、ちょっと落ち着け、モちつけ、、、、

解決策③ (これで解決!)

App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import SignIn from "./Auth/SignIn";


   <Router>
      <Header />
      <Route exact path="/Auth/SignIn" component={SignIn} />
    </Router>
components/Header.js
import { Link } from "react-router-dom";


            <Link to="/Auth/SignIn">Signin</Link>

今回はこれで解決しました!?

LinkとRouterをつないでいる感じですね

こちら先程の策と比べながら、今回大切にして欲しい3つのポイントを解説していきますね。



①react-routerとreact-router-domが似ているようでちょっと違う件について

まず、今回import したのはreact-router-dom です。

先程はreact-routerでしたね。


これは何が違うねん!

まぁまぁ一回落ち着け、モちつけ、、、、、、(お気に入りのボケです。)

簡単な違いはというと、
react-router-domはBrowserRouterをRouterと一緒に使い、
react-routerは historyをRouterと一緒に使うということです。

詳しい解決や記述方法はreact-routerとreact-router-domこちらの記事を御覧ください!

まぁあまり大きな違いは無いようです。

② as の効果範囲
今回僕が BrowserRouter をimportする際に用いたasですが、
これの影響範囲は、例えばこの記述だと、どこまでだと思いますか?

import { BrowserRouter as Router, Route, Switch } from “react-router-dom”;

正解は、、、

Routerだけです。☆彡

以上。

③ componentsについて

<Route exact path="/Auth/SignIn" component={SignIn} />

まず、exactの記述に関して

pathに指定したルートと全く同じところに移動する感じです。

exactをつけないと違うルートに行ってしまう可能性があるためつけたほうがいいです。



componentの記述について

指定したconponentsを表示します。
今回はSignIn componentsを表示
表示したいcomponentsを import し忘れないように注意。

以上で簡単なルーティングの設定方法の解説を終わりにします。

最後に、
この記事を見る限り、v3 から v4への移行期に、色々と新しいのが出たり、
古いのが消えていたりしているそうなので、念の為、検索する際は確認してみたほうが無難かもしれないですね
react-router v3からv4へのマイグレーション
BrowserRouter

今回の参考文献【React】ルーティング設定方法

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

[React] ルーティング設置の仕方に悩んでいる方。ルーティングのエラー解決と解説

どうもこんにちわ

今回は、Reactのルーティングを設定する上でよく目にする

Error: Invariant failed: You should not use <Link> outside a <Router>

というエラーについて、僕自身調べてみて

Typescript中心のエラーの解決法が多いや、、。

と思ったので、シンプルなReactでの説明をしていきたいと思います。
また、解決に至るまでのプロセスで出会った3つの関数?(っていうのかな)たちも
どんどん解説していくので、ぜひ最後まで見ていってください。

まず僕がこのエラーに引っかかった時のコードかこちら、

App.js
import React from "react";
import { Link } from "react-router-dom";
export default function Home() {

  return (
    <div className="container">
      <title>Hands on Mania</title>
      <main className="u-text-center">
        <h1 className="font-family-homemade">Hands on Mania</h1>
        <p className="description">studying</p>  
        <Link to="/Auth/SignIn">signin</Link>
      </main>
    </div>
  );
}

策①   Memoryrouterを使う

最初は、react-router-domから「Memoryrouter」をインポートして

        <Memoryrouter>
          <Link to="/Auth/SignIn">signin</Link>
        </Memoryrouter>

このようにしましょう。との解決策でした

早速試したのですが、、結果は変わらず。。

まぁまぁ焦らず、ゆっくり行こーや。
ここで解決できてたらQiitaの記事にはしてません。

では簡単に解説します。

Memoryrouterについて簡単に調べたところ、

テストおよびReact Nativeのような非ブラウザー環境で役立ちます。

と調べて一番上にあった記事で解説されていたので、
テストコードを書く時。または、ReactNative環境で普通は使うものなのかな?
と思いました。

Memoryrouterと似たような(?)ものにhooksのuseMemoが思い出されたので、
こちらも申し訳程度に紹介。

React公式によれば

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

このような公式で使い、あくまで最適化のために使うそうだ
初心者にはまだ早い領域かもしれないですね?

策② Route と Switch を使う

こちらはgithubに乗っていた解決策だったので、
これはいけるっしょ。と内心ヨユーな表情で思っていたのですが、
あっけなく予想を裏切られました。
(僕のやり方が違う可能性が高い、、。)

方法としては、このように記述するのさっ。というやり方です。

import { Route, Switch } from "react-router";


   <Switch>
      <Route exact path="/Auth/SignIn" />
   </Switch>

しかし、結果は儚くとも同じようなエラー内容。。

Error: Invariant failed: You should not use <Switch> outside a <Router>

だめだー、、。
俺の人生終わったー、、。

諦めないで!
そうなるのはまだ早い。

次で解決策を提示しながら一緒に解説するから、ちょっと落ち着け、モちつけ、、、、

解決策③ (これで解決!)

App.js
import { BrowserRouter as Router, Route } from "react-router-dom";
import SignIn from "./Auth/SignIn";


   <Router>
      <Header />
      <Route exact path="/Auth/SignIn" component={SignIn} />
    </Router>
components/Header.js
import { Link } from "react-router-dom";


            <Link to="/Auth/SignIn">Signin</Link>

今回はこれで解決しました!?

LinkとRouterをつないでいる感じですね

こちら先程の策と比べながら、今回大切にして欲しい3つのポイントを解説していきますね。



①react-routerとreact-router-domが似ているようでちょっと違う件について

まず、今回import したのはreact-router-dom です。

先程はreact-routerでしたね。


これは何が違うねん!

まぁまぁ一回落ち着け、モちつけ、、、、、、(お気に入りのボケです。)

簡単な違いはというと、
react-router-domはBrowserRouterをRouterと一緒に使い、
react-routerは historyをRouterと一緒に使うということです。

詳しい解決や記述方法はreact-routerとreact-router-domこちらの記事を御覧ください!

まぁあまり大きな違いは無いようです。

② as の効果範囲
今回僕が BrowserRouter をimportする際に用いたasですが、
これの影響範囲は、例えばこの記述だと、どこまでだと思いますか?

import { BrowserRouter as Router, Route, Switch } from “react-router-dom”;

正解は、、、

Routerだけです。☆彡

以上。

③ componentsについて

<Route exact path="/Auth/SignIn" component={SignIn} />

まず、exactの記述に関して

pathに指定したルートと全く同じところに移動する感じです。

exactをつけないと違うルートに行ってしまう可能性があるためつけたほうがいいです。



componentの記述について

指定したconponentsを表示します。
今回はSignIn componentsを表示
表示したいcomponentsを import し忘れないように注意。

以上で簡単なルーティングの設定方法の解説を終わりにします。

最後に、
この記事を見る限り、v3 から v4への移行期に、色々と新しいのが出たり、
古いのが消えていたりしているそうなので、念の為、検索する際は確認してみたほうが無難かもしれないですね
react-router v3からv4へのマイグレーション
BrowserRouter

今回の参考文献【React】ルーティング設定方法

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

Drupalize.MeのReact & Drupal 8 Code Examplesをddevで起動する

これを起動する:https://github.com/DrupalizeMe/react-and-drupal-examples

バックエンド(Drupal 8)を起動

事前にcomposer、docker,ddevなど必要なライブラリをインストールしておく。

$ cd drupal
$ ddev start
$ ddev composer install
$ ddev import-db --src=./backup.sql.gz

ブラウザで https://react-tutorials-2.ddev.site にアクセスし、初期インストールを行う。

インストールプロフィールは standard、データベース情報は以下の通り入力する。これは drupal/.ddev 内で設定されているもので、 $ ddev start ですでに起動している。

項目
database db
username db
password db
host db
port 3306

最後に view existing site をクリックしてDrupalサイトに移動し、コンテンツやユーザー、構成などがバックアップデータベースから反映されているか確認。ユーザー名:admin、パスワード:admin で設定されているユーザーが存在するのでそれで管理者としてログインしてみる。

https://react-tutorials-2.ddev.site/admin/config/people/simple_oauth に移動して、 generate keys をクリックした後、drupal/keys ディレクトリに private.key と public.key が生成されているか確認。

スクリーンショット 2020-09-14 午後4.51.54.png

フロントエンド(React)を起動

$ cd react-decoupled
$ yarn install
$ yarn run start

ブラウザで http://localhost:3000 にアクセスし、以下の画像のようにDrupalの情報が反映されているか確認。

スクリーンショット 2020-09-14 午前11.46.44.png

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

初Reactでも能力Amplifyしてスマートな(?)チーム分けアプリを作ってみる

突然ですが現withコロナ時代においてチーム分け時が必要な時はないでしょうか。

例えば最近増えましたリモート会議ですと一人しか同時に話せないのでzoomのブレイクアウトルーム使うなどチームを分割することは良くあるのかなと思ってます :person_frowning: :person_frowning_tone1:
(ちなみに自分の所属するチームでは毎朝、朝会でランダムに2、3人のチームを組んで開発しております。Be Full Stackです。)

普通のチームわけであればrandomに割り振ればいいかと思いますが、下記のような状況で困ることがありました

  • なるべく均等に分けたいグループがある(たとえば下記)
    • 研究室MTGで各学年が均等に分かれて欲しい
    • 飲み会で1つのチームに各世代の人がいて欲しい
    • マルチファンクショナルなスクラムチームを結成する時に各分野(インフラ、バックエンド、フロントエンド、デザイナー、QA)の人がそれぞれれのチームに入って欲しい)
  • ランダムだと前回と同じ人と同じチームになってなかなか同じチームになれない人がいる

このようなことが何回かあって毎回手動で調整するのが面倒だなと思い、楽するために苦労するエンジニア精神でこの2つの問題を解消するwebアプリ作ってみました。

↓作ったもの
https://www.memo-team-gen.com/

ソースコード (拙いですが公開しました。aws-exports.js などセキュリティ的にまずそうなのは除いております。)
https://github.com/LittleWat/memorizing-team-generator-public

普段の業務はモバイルアプリなのでiOSやAndroid,バックエンドをいじることはあってもフロントをいじることはほとんどなく、フロントエンドは :thinking::thinking::thinking:状態だったのですがこれを良い機会にということでググり力 :up: :up: で開発しました。

時代はjsよりtsということで、初めて typescript を使ったので設定周りでつまづきまくりました。が、何とかバックもフロントも開発できバックもフロントもイケるts(js)の旨味を享受しました。typescript初めてではありましたが、null安全で型推論が多少ありmapはじめコレクションの処理がサポートされていたりしてswift, kotlinと似ており個人的に書きやすかったです。

いろいろググると、React, Vue, Angularがフロントエンド の三大レームワークとしてあり、Angularは後方互換性を消しまくってて開発辛いみたいな記事を散見したので選択肢から外し、ReactVueで迷い :thinking: 世界的にメジャーなのは React ということで React の学習から入りました。(のちのち Vue のチュートリアルも行ったのですが、今回のようにRedux使わなくても良さそうな小さいアプリだったらVueのほうが楽かもと感じました。)

では開発の流れや内容についてご紹介したいと思います。

開発の流れ

Reactの学習

適当にググって出るチュートリアルをやっているとReactは書き方がclassのものとfunction(Hooks使うもの)のものの大きく二種類があるのを知り、Hooksがナウいことをしったので何かまとまっている良さげな記事ないかなと調べておりますと下記の良記事に遭遇できました。

こちらで紹介されていた本(pdf)を買って勉強しました。

こちらの本、体系的にまとまっててお世話になりました :bow:
Boothのpdfの本ですが、紙の本は時代の流れについていけてない(hooks以前のものが多い)ので流行りすたり(競争)の激しいDeepLearing研究時代を彷彿とさせるものを感じました。

またUIのライブラリはbootstrapか何か使おうか迷っていましたが、material-ui がぱっと見充実しており、GoTのキャラが使われていたためmaterial-uiを使うことにしました。話それますが、GoTのキャラはDeepLearning研究でもこちらなどちらほら題材として使われていましたね。

開発

ナウい(お財布に優しい)構成で行きたいなと思い、firebaseは以前iOSアプリで一通り触ってみたので、今回は別のものがいいなということでawsのamplifyでサーバレスにいくことにしました。

フロントエンド

amplify hosting で行いました。masterにpushするとデプロイされる仕組みも簡単に構築でき、ドメイン名の取得も簡単に行えました。amplify楽で良いですね。

バックエンド

amplify apiGraphQLRestfulAPIの二種類選べます。

ナウさを求める私はGraphQLで行おうと思い、下記のAWS公式のチュートリアルを行いました。

このチュートリアル、実装物も具体的で図解もあり、分かりやすく勉強になりました(おすすめです!!)
AppSync使えば簡単にElastic Seachと連携できるというのも知りました。強いですね。

がしかし、実際のチームわけのモデルをしたところ、お金のかかる(CapacityUnitの料金が倍になる)DynamoDBのGlobasSecondaryIndexが自動で貼られてしまったり、思ったように動かなかったので断念しました。(さらなるGraphQLの勉強の必要性を感じました。。)

仕方がないので、ここで方向転換して普通の RestfulAPIでいくことにしました。

こちらもいろいろ選べたのですが、expressで開発することにしました。
フロントは create-react-app でtypescirptが選べたのですが、expressはデフォルトが jsでバックエンドもどうせなら統一して tsでいきたいなと思ったのですが、割と設定でハマったので後述してます。結論として何とかjsに変換してデプロイはできました。
(amplify、デフォルトを jstsか選べるようになることを期待してます。(自分でコミットせぇという話かもですが汗))

DynamoDBのテーブル設計

Dynamo(NoSQL)なのでRDSではアンチパターンとされる配列を格納しても良いかと思い、配列を格納する設計にしました。

設計にはNoSQL Workbench という下記のようなツールを用いました。

スクリーンショット 2020-09-22 21.41.18(2).png

このツール、dynamoのlocalやawsに簡単に設計したものを反映でき、テーブル定義のjsonも保存できて使いやすかったです^^

チームわけのアルゴリズム

randomsmart か選べるようにしました。

スマートなと言ってますが、実は100回ランダムにチーム生成してその中から スコア(frequency)が低いものを選ぶというシンプルな方法です。

スコア(frequency, freq.)の計算ですが、これまでのチームわけの履歴を見て最近チームになった人がいればいるほど高いみたいな計算してます。

簡単に数式で示しますと

 w_{ij} = 
\begin{eqnarray}
\left\{
\begin{array}{l}
1 (member_i とmember_j が同じチームの時) \\
0 (member_i とmember_j が異なるチームの時)
\end{array}
\right.
\end{eqnarray} \\
N: 過去の何回分まで見るか(max10回分見るようにしてます) \\
M: メンバーの総数

と置くと下記のような計算をしてます。

  freq. = \sum_{k=1}^N \sum_{i=1}^M \sum_{j=1}^M \frac{1}{k}w_{ij} \\

ソースコード的には下記のあたりです。
https://github.com/LittleWat/memorizing-team-generator-public/blob/939748cd58a5f4be415b9a65b1b417407d92f946/amplify/backend/function/memoteamgenec8c5c26/src/ts/service/adjacent-matrix-computer.ts#L58

具体例で説明しますと下記のように均等に分けたいグループがあるときに
スクリーンショット 2020-09-24 0.08.17.png

単純に直近10回で何回一緒のチームになったかが下記の隣接行列で、5回一緒になった人が数組ありますが、

nmat.png

下記の重み付けされた隣接行列で100(最大値を100にスケールしました)の組みは[麻生太郎、松本さん]ペアなので

wmat.png

次回は[麻生太郎、松本さん]が同じチームには絶対ならないだろうということでgenerationすると下記のようになり

スクリーンショット 2020-09-24 0.11.36.png

たしかに[麻生太郎、松本さん]は同じチームにならず、いい感じでチーム分けできていることが伝わるかと思います。

ぜひ使っていただいて何かフィードバックいただけると幸いです。 :bow: :bow:

Analytics

amplify add analytics で一瞬でAmazon Pinpointが使えるようになります。簡単ですね。。
GoogleAnalyticsやFirebaseAnalyticsの置き換えになりそうでしょうか。

まとめ

  • Amplifyは現在進行形で発展中(awsのUIもupdateされてますね。)
  • 個人開発は全部自分でやらないといけないので良い勉強になる(慣れない技術を使うと苦労も多いですが発見が多く楽しい)
  • 隣接行列の可視化のライブラリ探してて偶然見つけたapexcharts楽しい

将来的にやりたいこと

  • ランダム生成はログイン不要にしたい
  • 生成結果をslack通知

などありますがやるかは不明です。。

ハマったこと

いろいろあるのですが、誰かの参考になればと思い念のため残しておきます。

backendのfunctionを勝手にいじってうまくdeployできない

同じく困った人がいた模様

https://github.com/yayun-12/amplify-typescript

下記、私がミスしていたファイル構成(srctsファイルを置いた。)

➜ memoteamgenec8c5c26 git:(add-lambda-team-gen-api) ✗ tree . -L 2
.
├── amplify.state
├── dist
│   ├── app.js
│   ├── app.js.map
│   ├── config
│   ├── index.js
│   ├── index.js.map
│   ├── latest-build
│   ├── latest-build.zip
│   ├── models
│   ├── repository
│   ├── service
│   └── util
├── function-parameters.json
├── memoteamgenec8c5c26-cloudformation-template.json
├── package-lock.json
├── package.json
├── parameters.json
├── src
│   ├── app.ts
│   ├── config
│   ├── event.json
│   ├── index.ts
│   ├── models
│   ├── repository
│   ├── service
│   ├── swagger.yml
│   └── util
└── tsconfig.json

あるべき姿(デフォルトの姿): (srcjsファイル, package.jsonを置く。)

➜ memorizing-team-generator git:(add-lambda-team-gen-api) ✗ tree amplify/backend/function/tmp             
amplify/backend/function/tmp
├── amplify.state
├── function-parameters.json
├── parameters.json
├── src
│   ├── app.js
│   ├── event.json
│   ├── index.js
│   └── package.json
└── tmp-cloudformation-template.json

結局 srcの下に tsディレクトリを切って下記のようにtsconfig.jsonを編集すると動いたが、ディレクトリ構造が気持ち悪いことになりました...
何か良い方法ないのですかね :thinking:

tsconfig.json
{
  "compilerOptions": {
    "outDir": ".",
    "sourceMap": true,
    "module": "commonjs",
    "target": "es6",
    "lib": ["es2017", "dom"],
    "moduleResolution": "node",
    "removeComments": true,
    "allowJs": true,
    "checkJs": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": "./ts",
    "paths": {
      "*": ["node_modules/*", "ts/types/*"]
    }
  },
  "include": [
    "ts/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

responsive designにできない

こちらは今もハマってます。ただのフロントエンドスキル不足故の問題です。。cssの勉強などします。。

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