20200727のReactに関する記事は9件です。

Next.jsでコンポーネントの使い方を学んだ

コード

実際に簡単なコンポーネントを作成してみた

Component.tsx
import React from 'react';

export enum FontSize {
  Small,
  Large,
}
type props = {
  image: string;
  alt: string;
  size: FontSize;
};

export const Component: React.FC<props> = ({ image, alt, size }: props) => {
  let text;
  let message;
  let font;
  switch (size) {
    case FontSize.Small:
      font='font-hairline';
      message = 'This is fine print';
      break;
    case FontSize.Large:
      font='font-black';
      text = 'This is BOLD print';
      break;
  }
  return (
    <div className="w-full my-12 mx-auto text-center">
      <div className="w-1/2 m-auto">
        <img src={image} alt={alt} />
      </div>
      <div className={`${font}`}>{message}</div>
      <div className={`mb-1 ${font}`}>{text}</div>
    </div>
  );
};
Component.stories.tsx
import { Component, FontSize } from '~/components/molecules/review/countZero';
import React from 'react';

export default {
  title: 'molecules/review/component',
  component: Component,
};

export const SmallLetter = () => {
  return (
    <Component
      image={
        'https://1.bp.blogspot.com/-fF4CgmrOpJs/XnLn8I0oy0I/AAAAAAABX0w/nBUniFZ1Q68Qf5wSY6jbHRb66N8GqaPmQCNcBGAsYHQ/s1600/figure_gray_zone.png'
      }
      alt={'細字'}
      size={FontSize.Small}
    />
  );
};
export const LargeLetter = () => {
  return (
    <Component
      image={
        'https://3.bp.blogspot.com/-XJrKocsLwIk/VfS6gOoJvJI/AAAAAAAAxRM/oaow0Sye3PM/s800/mokuhyou_tassei_man.png'
      }
      alt={'太字'}
      size={FontSize.Large}
    />
  );
};

Componentルール

汎用性を高めるため、同じもの以外はpropsとして扱う。
Component.tsxで作ったpropsはStoriesのreturnで。
今回は2つしか出力していないが、出力する数が多いほどComponentの価値が出る。また、将来的に修正で増える可能性があるためコンポーネントにしておくと管理が楽になる。

実行結果

image.png

image.png

まとめ

先輩に勧められてOOPについての本を読んだとで、コンポーネントの価値理解がより深まった。
ただ既存のコードを修正をするだけでなく、自分で実際に1から作ることの大切さを学べた1週間だった。

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

React.jsでlocation.hrefを用いた外部URLへの遷移

React.jsにてクリックイベント等でページ遷移させたい場合、window.location.hrefが利用できます。
Vue.jsの場合はwindowオブジェクトからの指定は不要だったため、備忘録として残します。

Googleへ遷移させたい場合
window.location.href = "https://google.com";
reactでの使用例
import React from "react";

function Google() {
  const redirectToGoogle = () => {
    window.location.href = "https://google.com";
  };
  return (
    <div>
      <div onClick={redirectToGoogle}>Googleへ</div>
    </div>
  );
}

export default Google;

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

emotionを使うメリット

この記事について

社内プロジェクトでstyled-componentsが主に使われており、世間でもstyled-componentsが主流な感じなのですが、
徐々に伸びてきているemotionが気になったので導入して使ってみました。

お…!ってなったのでstyled-componentsに比べてよかったこと、悪かったことを簡単にまとめます。
(*随時更新)

emotion初心者なのでこんなメリット/デメリットある!ってことがあれば教えてください:innocent:

emotionとは

後発のCSS-in-Jsのライブラリです。
基本的にはstyled-componentsでできることはほぼできそうな印象を受けました。

個人的に良い!って思った点は
- TypeScriptとの相性◎ (https://emotion.sh/docs/typescript)
- render内(html)が見やすくなる

// styled-componentっぽく書いたりできます
import styled from '@emotion/styled'

const Pin = styled.div`
  color: red;
`

render(<Pin> pin component </Pin>)

でもこっちの書き方が便利です(TSの恩恵受けられる)

import styled from '@emotion/styled'

// Object Stylesっていいます

const Pin = css({
    color: 'ref',
});

render(<div css={Pin}> pin component </div>)

他にもいろんな書き方できるみたいです
https://emotion.sh/docs/introduction

メリット

TypeScriptと一緒に使ったとき

css構文のサポート受けられる

react書いてるとcss(っぽいの)をかくのめっちゃめんどくさいって思ってました。
これがあればちょっとストレス軽減!!
スクリーンショット 2020-05-01 15.54.25.png

コンパイル前に間違ったら叱ってくれる

タイポとか。地味に嬉しいやつです。
スクリーンショット 2020-05-01 15.53.49.png

かぶってるやつ教えてくれる

考えずに書いてるとこんなこともあったりするので…

スクリーンショット 2020-05-01 16.07.35.png

読みやすさ

styled-componentsを使っていると、React Componentとただのタグにstyleつけてるのが混ざって読みにくく感じていました。
緑ばっかりになっててしんどい:mask:

(
  <Header>
  <SiteTitle> ~~ hotel </SiteTitle>
  <Navigation>
   <NavigationList>
    <NavigationItem><a href="/plan">宿泊プラン</a></NavigationItem>
    <NavigationItem><a href="/price">料金案内</a></NavigationItem>
    <NavigationItem><a href="/reserve">宿泊予約</a></NavigationItem>
    <NavigationItem><a href="/access">交通アクセス</a></NavigationItem>
   </NavigationList>
  </Navigation>
 </Header>
);

emotionを使うといつものhtmlのタグにclassNameっぽくかけるので読みやすいです。
ここにreactComponentが追加されたら、
「あ、これ特別なやつ」ってなるので脳の負担が減るように感じました。

return (
  <header css={Header}>
  <h1 css={SiteTitle}> ~~ hotel </h1>
  <nav css={Navigation}>
   <ul css={NavigationList}>
    <li css={NavigationItem}><a href="/plan">宿泊プラン</a></li>
    <li css={NavigationItem}><a href="/price">料金案内</a></li>
    <li css={NavigationItem}><a href="/reserve">宿泊予約</a></li>
    <li css={NavigationItem}><a href="/access">交通アクセス</a></li>
   </ul>
  </nav>
 </header>

);

メディアクエリが簡単

styled-componentsの時は結構書き方めんどくさかったのですが、
emotionでは下記のようにかけます!

const Pin = css({
    color: 'red',
    '@media(min-width: 420px)': {
    color: 'orange'
    } 
});

デメリット

デメリットもいろいろあるみたいです。

create-react-appとの相性の悪さ

emitして設定を書き換えなきゃいけないのですが、無理矢理感があるので、create-react-appで進めるならstyled-componentsで進めたほうが良い気がします。
(結構めんどくさかったです)

経験した中では上記だけです:innocent:
あと、object stylesはTSの恩恵受けられるので書くのですが、書き方めんどくさい(小声)

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

react v15 -> v16へマイグレーション 

  • react v15.6.2 -> v16.13.1
  • react-dom v15.6.2 ->v16.13.1
  • react-router v3.0.3 -> v5.2.0
  • react-router-dom 5.2.0(追加)
  • styled-components v2.3.2 -> v5.1.1
  • react-responsive v2.0.0 -> v8.1.0
  • react-document-meta -> react-helmet(変更)
  • history v3.0.0 -> v5.0.0
  • react-slick ->v0.27.1

react

componentWillReceivePropsが廃止予定

今後削除される機能なので、ロジックを組み直したり, componentDidUpdateに置き換えたりなどの対応が必要です。
今回は急ぎなので、UNSAFE_componentWillReceiveProps に置き換えます。
componentWillReceivePropsと機能は同じです。後ほどリファクタリングします..!

- componentWillReceiveProps(nextProps) { ...
+ UNSAFE_componentWillReceiveProps(nextProps) {

componentWillMountが廃止予定

componentWillReceivePropsと同様です。こちらもUNSAFE_componentWillMount()にしてあとでリファク(

- componentWillMount() { ...
+ UNSAFE_componentWillMount() {

また、自分のコードでcomponentWillMount等の廃止予定ライフサイクルメソッドを使っていなくても、依存パッケージ内で使用されている場合がある。
その場合はパッケージをアップデートするか、アップデート対応していない場合はパッケージの入れ替えを行なった。

ルーティング

Link

Linkコンポーネントは react-routerからreact-router-domへ移動されたので、
import元を全て書き換えます。

- import { Link } from 'react-router'
+ import { Link } from 'react-router-dom'

browserHistoryの廃止

v4からbroserHistoryが廃止になっているので、置き換えが必要になります。

- <Router history={browserHistory}>
-  ...
- </Router>
+ <BrowserRouter>
+  ...
+  </BrouserRouter>
- const hashLocation = browserHistory.getCurrentLocation().hash.replace('#', '')
- const hashLocation = this.props.location.hash.replace('#', '')

子コンポーネントでlocationやhistoryを使いたい場合(propsで渡って来ない場合)は、
react-routerがhooks対応しているのでそちらを使えばバケツリレーにならずスッキリかけます。

- import React, { Component } from 'react'
- class Breadcrumb extends Component {
- componentDidMount() {
-   this.props.dispatch(breadcrumb(browserHistory.getCurrentLocation().pathname));

+ import React, { useEffect } from 'react'
+ const Breadcrumb = (props) => {
+  const location = useLocation()
+  useEffect(() => {
+   props.dispatch(breadcrumb(location.pathname))
+  }, [])

react-routerのhooksに関しては、下記の記事がわかりやすかったです。
React RouterがHooks対応したので使い方を整理する

store.jsx
import { history } from './user'

export default function configureStore(initialState) {
...

sagaMiddleware.run(rootSaga, { history })

styled-components

extend -> styled()

extendはv4から廃止されていたので、styled(Comp) の表記に統一します。

- const ButtonTertiary = Button.extend`
- ...
- `
+ const ButtonTertiary = styled(Button)`
+ ...
+ `

injectGlobal -> createGlobalStyle

injectGlobalというグローバルのCSSの記述が、v4からcreateGlobalStyleというAPIに取って代わられています。

- injectGlobal`
-@font-face {
-   ...
+ export const GlobalStyle = createGlobalStyle`
+ @font-face {
+  ...

react-router-scroll

SPAでは何らかの処理をしない限り、ページ遷移後もスクロールの位置が先頭に戻らない。
今回アップデートしたプロジェクトでは、 react-router-scrollが使われていた。
しかし、3年前から更新がかかっていない...
自分で実装した方が早いと判断し、パッケージを捨てることにする。

- import { useScroll } from 'react-router-scroll';
+ import ScrollTop from './containers/ScrollTop'

ルーティング部分
 <BrowserRouter>
    <ScrollTop>
      <Switch>
        <Route exact path="/" component={Top} />
      </Switch>
    </ScrollTop>
 </BrowserRouter>
ScrollTop.jsx
import { useEffect } from 'react'

export default function ScrollTop(props) {
  useEffect(() => {
    window.scrollTo(0, 0)
  }, [props.location])
  return props.children
}

実はredux周りなんかも入れたらもっと膨大な作業になりました...
こまめなアップデート、目指していきたいです!

参考資料

Styled Components v4について
[React]react-router v4で画面遷移時に前のページのスクロール位置が残る

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

React公式チュートリアルのクラスコンポーネントを関数コンポーネントに書き替える

なかなか手をつけられなかったReact公式サイトのチュートリアルを、一通りやってみた。
ステートフックuseState()を使って、クラスコンポーネントから関数コンポーネントに書き換えてみたのでメモに残す。

公式サイトより:チュートリアルReact の導入
三目並べ完成形

hookを呼び出す際に気をつけること => フックを呼び出すのはトップレベルのみ

フックをループや条件分岐、あるいはネストされた関数内で呼び出してはいけません。代わりに、あなたの React の関数のトップレベルでのみ呼び出してください。これを守ることで、コンポーネントがレンダーされる際に毎回同じ順番で呼び出されるということが保証されます。これが、複数回 useState や useEffect が呼び出された場合でも React がフックの状態を正しく保持するための仕組みです。

参照: React公式サイト フックのルール

Square(三目並べの正方形のマス目)

Square.jsx
import React from "react";

// function Square(props) {
//   return (
//     <button className="square" onClick={props.onClick}>
//       {props.value}
//     </button>
//   );
// }

// Board(盤面) コンポーネントから { onClick, value } = props を受け取っている
const Square = ({ onClick, value }) => {
  return (
    <button className="square" onClick={onClick}>
      {value}
    </button>
  );
};

export default Square;

Board(盤面)

Board.jsx
import React from "react";
// Square(三目並べの正方形のマス目)コンポーネントをimport
import Square from "./Square";

// class Board extends React.Component {
//   renderSquare(i) {
//     return (
//       <Square
//         value={this.props.squares[i]}
//         onClick={() => this.props.onClick(i)}
//       />
//     );
//   }

// Game コンポーネントから { squares, onClick } = props を受け取っている
const Board = ({ squares, onClick }) => {
  const renderSquare = i => {
    return (
      <Square
        value={squares[i]}
        onClick={() => {
          onClick(i);
        }}
      />
    );
  };

  // render() {
  return (
    <>
      <div className="board-row">
        {renderSquare(0)}
        {renderSquare(1)}
        {renderSquare(2)}
      </div>
      <div className="board-row">
        {renderSquare(3)}
        {renderSquare(4)}
        {renderSquare(5)}
      </div>
      <div className="board-row">
        {renderSquare(6)}
        {renderSquare(7)}
        {renderSquare(8)}
      </div>
    </>
  );
  // }
};

export default Board;

Game

Game(親)コンポーネントは props を使うことで子に情報を渡すことができた。こうすることで、子コンポーネントとの間で常に同期されるようになる。

Game.jsx
// useStateをimport
import React, { useState } from "react";
// Board(盤面)コンポーネントをimport
import Board from "./Board";
import "./style.css";

// class Game extends React.Component {
//   constructor(props) {
//     super(props);
const Game = () => {
  //  クラスのconstructor内で `this.state` の初期化をやめて `useState` フックを使う
  //  this.state = {
  //    history: [
  //      {
  //        squares: Array(9).fill(null)
  //      }
  //   ],
  // useState()を使って、関数コンポーネントに状態を持たせる
  // const [state変数, set関数] = useState(初期値)
  const [history, setHistory] = useState([
    {
      squares: Array(9).fill(null)
    }
  ]);
  // stepNumber: 0,
  const [stepNumber, setStepNumber] = useState(0);
  // xIsNext: true
  const [xIsNext, setXIsNext] = useState(true);
  //     };
  //   }

  // handleClick(i) {
  const handleClick = i => {
    // const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const historyCurrent = history.slice(0, stepNumber + 1);
    // const current = history[history.length - 1];
    const current = historyCurrent[historyCurrent.length - 1];
    // const squares = current.squares.slice();
    const squares = [...current.squares];

    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    // squares[i] = this.state.xIsNext ? "X" : "O";
    squares[i] = xIsNext ? "X" : "O";
    //this.setState({
    //  history: history.concat([
    //    {
    //      squares: squares
    //    }
    //  ]),

    setHistory([...historyCurrent, { squares }]);
    // stepNumber: history.length,
    setStepNumber(historyCurrent.length);
    // xIsNext: !this.state.xIsNext
    setXIsNext(!xIsNext);
  };
  //   });
  // }

  // jumpTo(step) {
  //   this.setState({
  //     stepNumber: step,
  //     xIsNext: (step % 2) === 0
  //   });
  // }

  const jumpTo = step => {
    setStepNumber(step);
    setXIsNext(step % 2 === 0);
  };
  // render() {
  // const history = this.state.history;
  // const current = history[this.state.stepNumber];
  const current = history[stepNumber];
  const winner = calculateWinner(current.squares);

  const moves = history.map((step, move) => {
    const desc = move ? `Go to move # ${move}` : `Go to game start`;
    return (
      <li key={move}>
        {/* <button onClick={() => this.jumpTo(move)}>{desc}</button> */}
        <button onClick={() => jumpTo(move)}>{desc}</button>
      </li>
    );
  });

  let status;
  if (winner) {
    status = `Winner : ${winner}`;
  } else {
    // status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    status = `Next Player : ${xIsNext ? "X" : "O"}`;
  }
  return (
    <div className="game">
      <div className="game-board">
        {/* <Board squares={current.squares} onClick={i => this.handleClick(i)} /> */}
        <Board squares={current.squares} onClick={i => handleClick(i)} />
      </div>
      <div className="game-info">
        <div>{status}</div>
        <ol>{moves}</ol>
      </div>
    </div>
  );
  // }
};

const calculateWinner = squares => {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
};

export default Game;

参考: React公式サイト

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

React アプリに Auth0 でシュッと認証を組み込んで Vercel に爆速デプロイする

Auth0 は認証・認可サービスをクラウドで提供している SaaS ベンダーです。

「認証」という機能はどのアプリケーションにも求められる重要な要件ですが、プロダクトの本質的なビジネス価値を持たない場合が多いでしょう。Auth0 を使用することで、この「認証」機能という Undifferentiated Heavy Lifting な作業を排除できます。

本記事では、簡単な React アプリケーションを作成して Auth0 を使用した認証機能を実装します。また、作成したアプリケーションを Vercel(旧 Zeit now)にデプロイする方法を解説します。ユーザのサインアップ後の確認メールなどは SendGrid から送信されます。

以下は、本記事で紹介するアプリケーションの簡単な構成図です。また、本記事で実装されたアプリケーションは Vercel にデプロイしています。こちらからアクセスできます。ソースコードは以下の GitHub リポジトリにホストしています。

https://github.com/daisuke-awaji/auth0-todo-app

auth0-vercel.png

React アプリケーションの雛形を作る

まずは create-react-app を使用して簡単なアプリケーションを実装していきます。

$ create-react-app app --typescript

react-router

$ yarn add react-router react-router-dom
$ yarn add -D @types/react-router @types/react-router-dom

最初の react-router はシームレスなナビゲーションを可能にするメインのライブラリです。ふたつめは react-router-dom で、React ルーターの DOM 結合を提供します。-D オプションをつけることで開発時にのみ使用するライブラリをインストールできます。この例では TypeScript の型定義ライブラリをインストールしています。

react-bootstrap

最低限のデザインを整えるために react-bootstrap を使用します。

$ yarn add react-bootstrap react-bootstrap-icons bootstrap
$ yarn add -D @types/react-bootstrap

auth0-react

Auth0 の SDK である auth0-react をインストールします。後述しますが、従来の auth0-spa-js などに比べると Hooks 化の対応が進んでおり、本当に最小限のコードで認証機能を実装できます。

$ yarn add @auth0/auth0-react

auth0-react を使用して認証機能を実装する

auth0-react の公式ドキュメント でも解説していますが、本記事ではもう少し実際の開発のユースケースを想定してガイドします。

Auth0Provider

まずは index.tsx にて <App/> コンポーネントを <Auth0Provider/> でラップします。このようにしておくことで、 <App/> コンポーネント内で useAuth0 フックを使用できるようになります。 useAuth0 フックを使用することで、以下のような様々な認証に関するステートおよびメソッドを取得することができます。

  • ステートの例: isLoading, isAuthenticated, user
  • メソッドの例: loginWithRedirect, logout
index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { Auth0Provider } from "@auth0/auth0-react";
import "bootstrap/dist/css/bootstrap.min.css";
import { App } from "./App";

ReactDOM.render(
  <Auth0Provider
    domain={process.env.REACT_APP_AUTH0_DOMAIN!}
    clientId={process.env.REACT_APP_AUTH0_CLIENT_ID!}
    redirectUri={window.location.origin}
  >
    <App />
  </Auth0Provider>,
  document.querySelector("#root")
);

domain および、clientId には Auth0 のマネジメントコンソール画面で作成した Application の domain, clientId を入力してください。

image.png

LoginButton

ログインボタンは useAuth0 フックを使用すると簡単に実装できます。loginWithRedirect メソッドを実行すると Auth0 の SSO エンドポイントを使用してログインした後に、自身のアプリケーションにリダイレクトします。

LoginButton.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";

function LoginButton() {
  const { isAuthenticated, loginWithRedirect } = useAuth0();

  return !isAuthenticated ? (
    <Button onClick={loginWithRedirect}>Log in</Button>
  ) : null;
}

export default LoginButton;

実際にログインボタンを押した振る舞いは以下のようになります。
login.gif

ログイン処理の際、Auth0 のエンドポイントにアクセスし、自身のアプリケーションにリダイレクトします。事前に Application URIs の各種設定をしておきましょう。以下の例では開発用に localhost:3000、 本番デプロイ用として https://auth0-todo-app.vercel.app/ を設定しています。

image.png

LogoutButton

ログアウト処理には logout メソッドが用意されています。
ログアウト後に表示するパスを returnTo: xxxx で指定します。

LogoutButton.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";

function LogoutButton(props: any) {
  const { isAuthenticated, logout } = useAuth0();

  return isAuthenticated ? (
    <Button
      variant="outline-primary"
      onClick={() => {
        logout({ returnTo: window.location.origin });
      }}
      {...props}
    >
      Log out
    </Button>
  ) : null;
}

export default LogoutButton;

UserProfile

ログインユーザの情報は user を使用して取得します。認証プロバイダー(Google, Facebook, etc...)によって取得できる情報は異なります。以下では、ログインユーザのアバター画像を user.picture として取得しています。

UserProfileAvatar.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Image } from "react-bootstrap";
export const UserProfileAvatar = (props: any) => {
  const { user } = useAuth0();
  return (
    <Image
      style={{ width: "30px" }}
      src={user.picture}
      roundedCircle
      {...props}
    />
  );
};

この記事のサンプルアプリケーションでは、この情報を組み合わせて、ユーザアイコンをクリックすると各種メニューが表示されるようにしています。

image.png

認証済みのユーザだけ見れるページを制御する

react-router-dom を使用した実装ではおなじみの PrivateRoute を組み込んでいきます。
auth0-react では、ログインしていないユーザが表示しようとすると、ログインページにリダイレクトする withAuthenticationRequired() が用意されています。

以下のように ProtectedRoute のようなルート制御コンポーネントを作成しておくと便利です。

ProtectedRoute.tsx
import { withAuthenticationRequired } from "@auth0/auth0-react";
import React from "react";
import { Route } from "react-router-dom";

export function ProtectedRoute({ component, ...args }: any) {
  return (
    <Route component={withAuthenticationRequired(component, {})} {...args} />
  );
}

ルート制御ではこの ProtectedRoute コンポーネントを使用して、ログインしていないユーザがアクセスできないページを実装できます。以下の例では、/profile はログインしていない状態では表示できません。

App.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
import Home from "./pages/Home";
import { ProtectedRoute } from "./components/auth/ProtectedRoute";
import Profile from "./pages/Profile";
import { Layout } from "./components/layout/Layout";
import Support from "./pages/Support";
export function App() {
  const { isLoading } = useAuth0();
  if (isLoading) {
    return <p></p>;
  }

  return (
    <Router>
      <Layout>
        <Switch>
          <Route path="/" exact component={Home} />
          <ProtectedRoute path="/profile" component={Profile} />
          <Route path="/support" exact component={Support} />
        </Switch>
      </Layout>
    </Router>
  );
}

/profile にアクセスしようとするとログインページにリダイレクトされています。

protectedroute.gif

Vercel にデプロイする

Vercel のダッシュボードから Import Project をクリックします。

image.png

今回は GitHub にホスティングしているソースコードを元にデプロイするので、Git リポジトリを選択します。

image.png

GitHub のリポジトリ URL を指定してください。
image.png

最後にビルドコマンドと環境変数を設定します。
Auth0 の domain と clientId は環境変数に指定して React アプリに渡しましょう。

image.png

あとは Deploy ボタンをクリックして完了です。これだけでデプロイができるはずです。

サインアップ後の確認メールを SendGrid から送信する

Auth0 には Welcome メールやパスワードリセット、アカウントの検証などのためにメールを送信する仕組みが用意されています。

image.png

送信するためには事前にメールプロバイダの設定が必要です。今回は無料で提供されている SendGrid を使用してみましょう。

Email Provider の画面から  SendGrid  を選択し、送信元メールアドレス(From)と SendGrid の API キーを入力します。API キーは SendGrid の settings から発行できます。

image.png

ログインに成功すると以下のようなメールが送信されるようになります。

image.png

まとめ

Auth0 + Vercel + SendGrid という構成をチュートリアル的に実装し、その振る舞いを確認してみました。auth0-react ライブラリは非常に使い勝手がよく、簡単に認証機能を組み込むことができます。一連の流れを実装しても 1 ~ 2 時間で実装できました。以前までは苦労して認証の仕組みを整えていましたが随分と楽になりそうです。

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

React アプリ に Auth0 でシュッと認証を組み込んで Vercel に爆速デプロイする

Auth0 は認証・認可サービスをクラウドで提供している SaaS ベンダーです。

「認証」という機能はどのアプリケーションにも求められる重要な要件ですが、プロダクトの本質的なビジネス価値を持たない場合が多いでしょう。Auth0 を使用することで、この「認証」機能という Undifferentiated Heavy Lifting な作業を排除できます。

本記事では、簡単な React アプリケーションを作成して Auth0 を使用した認証機能を実装します。また、作成したアプリケーションを Vercel(旧 Zeit now)にデプロイする方法を解説します。ユーザのサインアップ後の確認メールなどは SendGrid から送信されます。

以下は、本記事で紹介するアプリケーションの簡単な構成図です。また、本記事で実装されたアプリケーションは Vercel にデプロイしています。こちらからアクセスできます。ソースコードは以下の GitHub リポジトリにホストしています。

https://github.com/daisuke-awaji/auth0-todo-app

auth0-vercel.png

React アプリケーションの雛形を作る

まずは create-react-app を使用して簡単なアプリケーションを実装していきます。

$ create-react-app app --typescript

react-router

$ yarn add react-router react-router-dom
$ yarn add -D @types/react-router @types/react-router-dom

最初の react-router はシームレスなナビゲーションを可能にするメインのライブラリです。ふたつめは react-router-dom で、React ルーターの DOM 結合を提供します。-D オプションをつけることで開発時にのみ使用するライブラリをインストールできます。この例では TypeScript の型定義ライブラリをインストールしています。

react-bootstrap

最低限のデザインを整えるために react-bootstrap を使用します。

$ yarn add react-bootstrap react-bootstrap-icons bootstrap
$ yarn add -D @types/react-bootstrap

auth0-react

Auth0 の SDK である auth0-react をインストールします。後述しますが、従来の auth0-spa-js などに比べると Hooks 化の対応が進んでおり、本当に最小限のコードで認証機能を実装できます。

$ yarn add @auth0/auth0-react

auth0-react を使用して認証機能を実装する

auth0-react の公式ドキュメント でも解説していますが、本記事ではもう少し実際の開発のユースケースを想定してガイドします。

Auth0Provider

まずは index.tsx にて <App/> コンポーネントを <Auth0Provider/> でラップします。このようにしておくことで、 <App/> コンポーネント内で useAuth0 フックを使用できるようになります。 useAuth0 フックを使用することで、以下のような様々な認証に関するステートおよびメソッドを取得することができます。

  • ステートの例: isLoading, isAuthenticated, user
  • メソッドの例: loginWithRedirect, logout
index.tsx
import React from "react";
import ReactDOM from "react-dom";
import { Auth0Provider } from "@auth0/auth0-react";
import "bootstrap/dist/css/bootstrap.min.css";
import { App } from "./App";

ReactDOM.render(
  <Auth0Provider
    domain={process.env.REACT_APP_AUTH0_DOMAIN!}
    clientId={process.env.REACT_APP_AUTH0_CLIENT_ID!}
    redirectUri={window.location.origin}
  >
    <App />
  </Auth0Provider>,
  document.querySelector("#root")
);

domain および、clientId には Auth0 のマネジメントコンソール画面で作成した Application の domain, clientId を入力してください。

image.png

LoginButton

ログインボタンは useAuth0 フックを使用すると簡単に実装できます。loginWithRedirect メソッドを実行すると Auth0 の SSO エンドポイントを使用してログインした後に、自身のアプリケーションにリダイレクトします。

LoginButton.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";

function LoginButton() {
  const { isAuthenticated, loginWithRedirect } = useAuth0();

  return !isAuthenticated ? (
    <Button onClick={loginWithRedirect}>Log in</Button>
  ) : null;
}

export default LoginButton;

実際にログインボタンを押した振る舞いは以下のようになります。
login.gif

ログイン処理の際、Auth0 のエンドポイントにアクセスし、自身のアプリケーションにリダイレクトします。事前に Application URIs の各種設定をしておきましょう。以下の例では開発用に localhost:3000、 本番デプロイ用として https://auth0-todo-app.vercel.app/ を設定しています。

image.png

LogoutButton

ログアウト処理には logout メソッドが用意されています。
ログアウト後に表示するパスを returnTo: xxxx で指定します。

LogoutButton.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Button } from "react-bootstrap";

function LogoutButton(props: any) {
  const { isAuthenticated, logout } = useAuth0();

  return isAuthenticated ? (
    <Button
      variant="outline-primary"
      onClick={() => {
        logout({ returnTo: window.location.origin });
      }}
      {...props}
    >
      Log out
    </Button>
  ) : null;
}

export default LogoutButton;

UserProfile

ログインユーザの情報は user を使用して取得します。認証プロバイダー(Google, Facebook, etc...)によって取得できる情報は異なります。以下では、ログインユーザのアバター画像を user.picture として取得しています。

UserProfileAvatar.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Image } from "react-bootstrap";
export const UserProfileAvatar = (props: any) => {
  const { user } = useAuth0();
  return (
    <Image
      style={{ width: "30px" }}
      src={user.picture}
      roundedCircle
      {...props}
    />
  );
};

この記事のサンプルアプリケーションでは、この情報を組み合わせて、ユーザアイコンをクリックすると各種メニューが表示されるようにしています。

image.png

認証済みのユーザだけ見れるページを制御する

react-router-dom を使用した実装ではおなじみの PrivateRoute を組み込んでいきます。
auth0-react では、ログインしていないユーザが表示しようとすると、ログインページにリダイレクトする withAuthenticationRequired() が用意されています。

以下のように ProtectedRoute のようなルート制御コンポーネントを作成しておくと便利です。

ProtectedRoute.tsx
import { withAuthenticationRequired } from "@auth0/auth0-react";
import React from "react";
import { Route } from "react-router-dom";

export function ProtectedRoute({ component, ...args }: any) {
  return (
    <Route component={withAuthenticationRequired(component, {})} {...args} />
  );
}

ルート制御ではこの ProtectedRoute コンポーネントを使用して、ログインしていないユーザがアクセスできないページを実装できます。以下の例では、/profile はログインしていない状態では表示できません。

App.tsx
import React from "react";
import { useAuth0 } from "@auth0/auth0-react";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
import Home from "./pages/Home";
import { ProtectedRoute } from "./components/auth/ProtectedRoute";
import Profile from "./pages/Profile";
import { Layout } from "./components/layout/Layout";
import Support from "./pages/Support";
export function App() {
  const { isLoading } = useAuth0();
  if (isLoading) {
    return <p></p>;
  }

  return (
    <Router>
      <Layout>
        <Switch>
          <Route path="/" exact component={Home} />
          <ProtectedRoute path="/profile" component={Profile} />
          <Route path="/support" exact component={Support} />
        </Switch>
      </Layout>
    </Router>
  );
}

/profile にアクセスしようとするとログインページにリダイレクトされています。

protectedroute.gif

Vercel にデプロイする

Vercel のダッシュボードから Import Project をクリックします。

image.png

今回は GitHub にホスティングしているソースコードを元にデプロイするので、Git リポジトリを選択します。

image.png

GitHub のリポジトリ URL を指定してください。
image.png

最後にビルドコマンドと環境変数を設定します。
Auth0 の domain と clientId は環境変数に指定して React アプリに渡しましょう。

image.png

あとは Deploy ボタンをクリックして完了です。これだけでデプロイができるはずです。

サインアップ後の確認メールを SendGrid から送信する

Auth0 には Welcome メールやパスワードリセット、アカウントの検証などのためにメールを送信する仕組みが用意されています。

image.png

送信するためには事前にメールプロバイダの設定が必要です。今回は無料で提供されている SendGrid を使用してみましょう。

Email Provider の画面から  SendGrid  を選択し、送信元メールアドレス(From)と SendGrid の API キーを入力します。API キーは SendGrid の settings から発行できます。

image.png

ログインに成功すると以下のようなメールが送信されるようになります。

image.png

まとめ

Auth0 + Vercel + SendGrid という構成をチュートリアル的に実装し、その振る舞いを確認してみました。auth0-react ライブラリは非常に使い勝手がよく、簡単に認証機能を組み込むことができます。一連の流れを実装しても 1 ~ 2 時間で実装できました。以前までは苦労して認証の仕組みを整えていましたが随分と楽になりそうです。

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

[React] React Hooksでどのdependencyが変化したのかわからないだって? そう、そんなときはusePreviousだ!

こんにちは。 @nerikosans です。みなさん、React Hooksしていらっしゃいますでしょうか。
今日は僕がHooksで詰まって3時間溶かして学んだことのメモを書いていこうと思います。

悲劇

例えば arrow function があったとして、それをhooksのdepsに入れてしまうと各render毎に異なるオブジェクトとして認識されてしまうのは日本書紀にも記述がありますね。
つまり

const getMySuperBenriFunction = () => {
  return (a: number, b: number) => a + b;
}

const benri1 = getMySuperBenriFunction();
const benri2 = getMySuperBenriFunction();

console.log(benri1 === benri2)  // => false

であり、

import getMySuperBenriFunction from './getMySuperBenriFunction';

const MySupremeComponent: React.FC = () => {
  const benri = getMySuperBenriFunction();
  const [v1, setV1] = React.useState(1);
  const [v2, setV2] = React.useState(3);

  // v1, v2が変わると出力
  // benriが変化することは想定してない
  React.useEffect(() => {
    console.log(benri(v1, v2));
  }, [v1, v2, benri]);

  return (
    <div>
      (ここにすごいUI)
    </div>
  )
}

みたいなことをすると全renderで useEffect が走って非常につらい気持ちになります。

デバッグ策

さて、前述のarrow functionの件はもちろんヤマトタケルの時代から決まっているのですが、うっかりこれを失念してしまったとしましょう。

そんなとき、「なぜこの useEffect が走るのかわからない...どのdepsが変化しているんや...」という調査を比較的ラクにできる手法を紹介します。
つまり、変数の値を前サイクルと比較できればよいのです。

ということでご登場願いましょう、usePreviousさーん!

usePreviousさん

function usePrevious<T>(value: T) {
  const ref = React.useRef<T>();
  React.useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

usePreviousさんの勇姿


function usePrevious<T>(value: T) { /*...*/ }

const MySupremeComponent: React.FC = () => {
  /* ... */
  console.log(usePrevious(benri) === benri) // => false

  React.useEffect(() => {
    console.log(benri(v1, v2));
  }, [v1, v2, benri]);

  return (
    {  /*...*/ }
  )
}

ということで、 usePrevious さんを用いて前サイクルと比較すれば、どのdepsの変化でEffectが発火しているのか、比較的早く発見できるでしょう。最高!

usePreviousさんの出自

なお、御大はReact公式FAQで紹介されています。公式hookになる可能性もあるみたいです。

おまけ

hooksプロのみなさんには言うまでもないかと思いますが、この問題の解決策は

const getMySuperBenriFunction = () => {
  return React.useCallback((a: number, b: number) => a + b, []);
}

benri 自体をcallbackに入れることですね。

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

Ant Design で Menu を含んだ Layout.Header の高さを変更

Ant Design で Menu を含んだ Layout.Header の高さを変更

And Design の Layout にて Menu を含んだ実例の記述がありますが、
以下 CSS を適用することで任意の高さに変更します。 (記述は SCSS です)

antd-layout-header-height-with-menu
// ここで Header 向けの任意の高さを指定
$header-height: 50px;
// Header に指定する line-height
// calc() を使用しているのは line-height の微調整が必要になるケースがあるため
$header-line-height: calc(#{$header-height} + 1px);

.ant-layout-header {
  height: $header-height;
  line-height: $header-line-height;
}

詳細

Layout により以下の記述をベースとします。

layout-base
import React from "react";

import { Layout, Menu } from "antd";

const { Header, Content, Footer } = Layout;

export default () => {
  return (
    <Layout className="layout">
      <Header>
        <div className="logo" />
        <Menu theme="dark" mode="horizontal" defaultSelectedKeys={["2"]}>
          <Menu.Item key="1">nav 1</Menu.Item>
          <Menu.Item key="2">nav 2</Menu.Item>
          <Menu.Item key="3">nav 3</Menu.Item>
        </Menu>
      </Header>
      <Content style={{ padding: "0 50px" }}>Content</Content>
      <Footer style={{ textAlign: "center" }}>Footer</Footer>
    </Layout>
  );
};

Header の height のみを変更

height を変えるので Header に style={{ height: 50 }} を指定してみます。

height-only
・・・
      <Header style={{ height: 50 }}>
・・・

すると、 Header の高さは変わりましたが Menu がそのままです。

height-only

これは以下のように Menu 自体には height は指定されておらず、
Menu の line-height が .ant-layout-header の inherit になっているためのようです。

menu-height

そこで・・・

Header にセレクタを指定したスタイルを適用

Header に対して CSS でのスタイル指定を行います。

header-styled
・・・
      <Header className="styled">
・・・

className="styled" を指定し、 CSS での記述を適用します。

header-styled
$header-height: 50px;
$header-line-height: calc(#{$header-height} + 1px);

.ant-layout-header.styled {
  height: $header-height;
  line-height: $header-line-height;
}

これで Header と Menu の高さが調整されました。

header-styled

CodeSandbox での実例

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