20200705のReactに関する記事は7件です。

Amplify で GraphQL と Cognito 認証

概要

はじめての Amplify デプロイまで の続きです。
前回のプロジェクト作成時点で認証に Cognito を使うようにしたり、mock で必要な設定を docker に入れています。
Cognito によるユーザー認証と GraphQL との連携を実装します。

ユーザー認証機能の追加

yarn add aws-amplify aws-amplify-react

App.tsx を以下のように変更します。
アカウント作成時に電話番号を不要にするために withAuthenticator の signUpConfig に hiddenDefaults を追加しています。

import React from 'react';
import Amplify from '@aws-amplify/core';
import awsmobile from './aws-exports';
import { withAuthenticator } from 'aws-amplify-react';
import '@aws-amplify/ui/dist/style.css';

Amplify.configure(awsmobile)

const App: React.FC = () => {
  return (
    <>
      <h1>タイトル</h1>
    </>
  )
}

//@ts-ignore
export default withAuthenticator(App, {
  signUpConfig: {
    hiddenDefaults: ['phone_number']
  }
});

※ import '@aws-amplify/ui/dist/style.css'; がないと認証画面のスタイルがない状態になるので必要でした
スクリーンショット 2020-07-05 10.33.51.pngスクリーンショット 2020-07-05 10.34.19.png

ログイン画面から Create account でアカウントを作成し、メールの認証をするとログインができます。
「タイトル」が表示されます。

GraphQL API の作成

まずは自動生成された schema.graphql の TODO モデルを試してみます。

schema.graphql
type Todo @model {
  id: ID!
  name: String!
  description: String
}

mock を利用することでローカルで検証ができます。

$ amplify mock api

http://localhost:20002 にアクセスすると以下のように表示されていると思います。
スクリーンショット 2020-07-05 10.53.48.png

TODO の作成
以下のように createTodo を実行しました。
スクリーンショット 2020-07-05 10.55.59.png

作成した TODO を取得してみます。
スクリーンショット 2020-07-05 10.57.37.png

react と GraphQL の連携

作成したデータを画面に表示してみます。

今回は docker でやっているので aws-exports.js の aws_appsync_graphqlEndpoint を
以下のように localhost に書き換えが必要です(ローカル開発中だけ)

aws-exports.js
"aws_appsync_graphqlEndpoint": "http://localhost:20002/graphql",

App.tsx を以下のように変更します。
Todo に保存したデータの1つを表示します。

App.tsx
import React, {useState} from 'react'
import Amplify from '@aws-amplify/core'
import awsmobile from './aws-exports'
import {withAuthenticator} from 'aws-amplify-react'
import '@aws-amplify/ui/dist/style.css'
import {listTodos} from './graphql/queries'
import API, {graphqlOperation} from '@aws-amplify/api'
import { Auth } from 'aws-amplify';

Amplify.configure(awsmobile)

const App: React.FC = () => {
  const [todos, setTodos] = useState<any>({})

  const getTodo = async() => {
    try {
      const res: any = await API.graphql(graphqlOperation(listTodos))
      console.log(res)
      setTodos(res.data.listTodos.items)
    } catch (e) {
      console.log(e)
    }
  }

  const ShowTodo = () => {
    if (todos.length > 0) {
      const todo = todos[0]
      return (
        <>
          <p>{ todo.id }</p>
          <p>{ todo.name }</p>
          <p>{ todo.description }</p>
          <p>{ todo.createdAt }</p>
          <p>{ todo.updatedAt }</p>
        </>
      )
    }
    return (
      <></>
    )
  }

  return (
    <>
      <h1>Todos</h1>
      <button onClick={() => getTodo()}>get todos!</button>
      <div>
        <ShowTodo/>
      </div>
    </>
  )
}
...

get todos! ボタンを押すと下に取得したデータが表示されます。
スクリーンショット 2020-07-05 15.17.03.png

cognito group による制御

AWS コンソールの Cognito で、グループを作成し、最初のユーザー認証で作成したアカウントに member グループを割り当てます。
スクリーンショット 2020-07-05 15.30.45.png

schema.graphql を以下のように変更します。

schema.graphql
type Todo
  @model
  @auth(rules: [
    { allow: owner },
    { allow: groups, groups: ["admin"], operations: [read, update] }
  ])
{
    id: ID!
    name: String!
    description: String
}

これにより、member 権限のアカウントは Todo に対してなにもできなくなります。
一度ログアウトするために App.tsx を以下のように変更します。

App.tsx
import React, {useState} from 'react'
import Amplify from '@aws-amplify/core'
import awsmobile from './aws-exports'
import {withAuthenticator} from 'aws-amplify-react'
import '@aws-amplify/ui/dist/style.css'
import {listTodos} from './graphql/queries'
import API, {graphqlOperation} from '@aws-amplify/api'
import { Auth } from 'aws-amplify';

Amplify.configure(awsmobile)

const App: React.FC = () => {
  const [todos, setTodos] = useState<any>({})

  const signOut = () => {
    Auth.signOut()
  }

  const getTodo = async() => {
    try {
      const res: any = await API.graphql(graphqlOperation(listTodos))
      console.log(res)
      setTodos(res.data.listTodos.items)
    } catch (e) {
      console.log(e)
    }
  }

  const ShowTodo = () => {
    if (todos.length > 0) {
      const todo = todos[0]
      return (
        <>
          <p>{ todo.id }</p>
          <p>{ todo.name }</p>
          <p>{ todo.description }</p>
          <p>{ todo.createdAt }</p>
          <p>{ todo.updatedAt }</p>
        </>
      )
    }
    return (
      <></>
    )
  }

  return (
    <>
      <button onClick={ () => signOut() }>ログアウト</button>
      <h1>Todos</h1>
      <button onClick={() => getTodo()}>get todos!</button>
      <div>
        <ShowTodo/>
      </div>
    </>
  )
}
...

以下のような画面になります。
一度ログアウトをすると、ログイン画面に遷移するので再度ログインします。
get todos! ボタンを押しても表示は変わらず、データが0件であることがわかります。
スクリーンショット 2020-07-05 20.09.15.png
例えば、ここで schema.graphql の groups を admin から member に変更すると、データが取得できることを確認できます。

また、画面表示をグループごとに分けたいので、App.tsx に以下を追加しました。
初回でログインユーザーのグループ情報を取得し、その情報をもとに表示を出し分けました。
useEffect で初回のみユーザー情報を取得します(第2引数に [] を入れないと無限ループします)

App.tsx
  const [group, updateGroup] = useState<any>({})

  const getUser = async() => {
    const user =  await Auth.currentAuthenticatedUser();
    updateGroup(user.signInUserSession.accessToken.payload["cognito:groups"])
    console.log(user.signInUserSession.accessToken.payload["cognito:groups"])
  }

  useEffect(() => {
    getUser()
  }, [])

  return (
    <>
      {
        group === 'admin' ? (
          <h1>管理者画面</h1>
        ) : (
          <h1>メンバー画面</h1>
        )
      }
      <button onClick={ () => signOut() }>ログアウト</button>
      <h1>Todos</h1>
      <button onClick={() => getTodo()}>get todos!</button>
      <div>
        <ShowTodo/>
      </div>
    </>
  )

スクリーンショット 2020-07-05 19.54.16.png

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

日本一わかりやすいReact-Redux入門#4~#7 学習備忘録

はじめに

この記事は、Youtubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact-Redux入門』の学習備忘録です。

前回講座で学んだ React に続き、大規模アプリ開発の必須のライブラリである Redux についても勉強をしていきます。

前回の記事はこちら

#4...Actionsを書いてstateの変更を依頼しよう

  • Actionsは、Fluxフローにおいて窓口の役割を果たす。
  • アプリから受け取った「stateの変更依頼」をReducersに渡す
  • Actionsはプレーンなオブジェクトを返す
src/Reducks/users/action.js
export const SIGN_IN = "SIGN_IN";
export const signInAction = {userState} => {
  return {
    type: "SIGN_IN",
    payload: {
      isSignedIn: true,
      uid: userState.uid,
      username: userState,username
    }
  }
};

export const SIGN_OUT = "SIGN_OUT";
export const signOutAction = () => {
  return {
    type: "SIGN_OUT",
    payload: {
      isSignedIn: false,
      uid: "",
      username: ""
    }
  }
}

5...Reducersの作り方とスプレッド構文の使い方

  • ReducersはActionsからデータを受け取り、Storeのstateをどう変更するか決める。
  • Reducersは、Storeの”現在の状態”と”初期の状態”の情報を保有する。
  • 初期状態の定義は、initialState.jsで行う。
src/reducks/store/initialState.js
const initialState = {
  users: {
    isSignedIn: false,
    uid: "",
    username: ""
  }
};

export default initialState
src/users/reducers.js
import * as Actions from './actions'
import initialState from '../store/initialState'

export const UsersReducer = (state = initialState.users, action) => {
  switch (action.type) {
    case Actions.SIGN_IN:
      return {
        ...state,
        ...action.payload
      }
      default:
        return state
  }
}

...はスプレッド構文というjavascriptの文法で、オブジェクトの展開を表す。すなわち、return文は下記と同じ意味になる・

      return {
        isSignedIn: false,
        uid: "",
        username: ""
        isSignedIn: true,
        uid: userState.uid,
        username: userState,username
      }

同じkeyのものが繰り返された時は、後半にあるものが上書きして残る。

#6...Redux(Store)とReactを接続してstateを変更しよう

  • Storeはstateを保存する役割。Reducersにより、Store内部のstateの値を変更される。
src/reducks/store/store.js
import {
  createStore as reduxCreateStore,
  combineReducers,
} from 'redux';

import {UsersReducer} from '../users/reducers';

export default function createStore() {
  return reduxCreateStore(
    combineReducers({
      users: UsersReducer,
    })
  );
}

  • combineReducers()は、複数のReducerを束ねる関数。今はUsersReducerしかないが、Reducerが増えた場合、ここの記述が複数行になる。

storeをReactアプリと接続するため、index.jsを編集する。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import createStore from './reducks/store/store';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

export const store = createStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
  • export const store = createStore();で、Storeを生成。
  • <Provider>タグで<App />をラッピングすることで、StoreをReactアプリに渡す。

本当にstoreとReactアプリが接続されたのかを、Redux Hooksを用いて確認する。

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import {useDispatch. useSelector} from "react-redux";

function App() {
  const dispatch = useDispatch()
  const selector = useSelector((state) => state)

  console.log(selector.users)

  return (
...

localhost:3000をGoogle Chromeで開き、検証ツールからconsole.log()の結果を見に行く。

image.png

initialStateとして定義しているuserオブジェクトの初期値が、Reactアプリに渡っているのが分かります!

次に、このstateの値を更新してみます。src/reducks/users/actions.jsで定義したsignInAction()が発火するボタンをApp.js内に追記します。

src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import {useDispatch, useSelector} from "react-redux";
import {signInAction} from "./reducks/users/actions"

function App() {
  const dispatch = useDispatch()
  const selector = useSelector((state) => state)

  console.log(selector.users)

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        <button onClick={() => dispatch(signInAction({uid: "0001", username: "torahack"})) } >
          Sign In
        </button>
      </header>
    </div>
  );
}

export default App;

<button>タグのonClickイベントに、先ほどのsignInAction()を渡しています。このボタンをクリックすると、

image.png

stateの値が更新されているのが分かります!

#7...URLに応じたコンポーネントを表示しよう

connected-react-routerというReduxライブラリを使用する。これは、Reduxのstoreを利用してルーティングを管理するというもの。

store.jsindex.jsを編集する。

src/reducks/store/store/js
import {
  createStore as reduxCreateStore,
  combineReducers,
  applyMiddleware
} from 'redux';
import {connentReactRouter, routerMiddleware} from "connected-react-router";

import {UsersReducer} from '../users/reducers';

export default function createStore(history) {
  return reduxCreateStore(
    combineReducers({
      router: connentReactRouter(histroy),
      users: UsersReducer,
    }),
    applyMiddleware(
      routerMiddleware(history)
    )
  );
}
  • createStore()が新たにhistoryという引数を受け取っている。これは、サイト遷移の履歴が入っている
  • routerというstate名で、このhistoryを管理する。
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import createStore from './reducks/store/store';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import * as History from 'history';

const histroy = History.createBrowserHistory();
export const store = createStore(history);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
  • <ConnectedRouter>でさらに<App />をラッピング。

具体的なルーティングの定義を実装する、実装は、Router.jsxという特殊なコンポーネントファイルを作成して行う。

src/Router.jsx
import React from 'react';
import {Route, Switch} from "react-router";
import {Login, Home} from "templates";

const Router = () => {
  return (
    <Switch>
      <Route exact path="/login" component={Login} />
      <Route exact path="(/)?" component={Home} />
    </Switch>
  );
};

export default Router
  • <Route>タグ内で、パスとコンポーネントの対応を記述する。
  • exact path=が完全一致、path=が部分一致。静的なルーティングに対しては前者を使い、動的なルーティングに対しては後者を使う("/posts/:id"のようなケース)

この時点では、templatesファイルは未作成。これを作る前に、App.jsを変更する。拡張子をApp.jsxにした上で、中身をがらっと変える。

src/App.jsx
import React from 'react'
import Router from './Router'

const App = () => {
  <main>
    <Router />
  </main>
};

export default App;

<main>タグでRouterコンポーネントを呼び出している。この<Router />の中身が、パスごとに切り替わるイメージ

続いて、templatesファイルを作る。templatesファイルは、静的ページにつき1個ずつ存在し、そのページ(ルーティング)において親コンポーネントの役割を果たす。

作成するファイルは以下の通り。
- src/templates/Login.jsx
- src/templates/Home.jsx
- src/templates/index.js

index.jsはエントリーポイントの役割を果たす。

src/templates/Login.jsx
import React from 'react';
import {useDispatch} from "react-redux";
import {push} from "connected-react-router";

const Login = () => {
  const dispatch = useDispatch();
  return (
    <div>
      <h2>ログイン</h2>
      <button onClick={() => dispatch(push('/'))}>
        ログインする
      </button>
    </div>
  );
};

export default Login
  • useDispatch()で store に紐付いた dispatch が取得できる。
  • push()を実行すると、引数のパスにページを遷移させることができる。現時点では、ダミーとしてルートパスを指定。
src/templates/Home.jsx
import React from 'react';

const Home = () => {
  return (
    <h2>Home</h2>
  );
};

export default Home
src/templates/index.js
export {default as Login} from './Login'
export {default as Home} from './Home'

ブラウザで確認すると、

path = "/" path = "/login"
image.png image.png

上手く表示されています!また、ログインボタンを押すことで、Home画面へ遷移できるようになっています。

おわり

今回はここまで。

第三回講座で概念を学んだFluxフローについて、実際に手を動かすことでなんとなーく分かってきた気がしてきました。

開発フレームワークにおける「どこに何を書くべきかが決まっている」という状態は、初期の学習コストは高くなりがちな反面、慣れることさえできれば、開発だけでなく保守・改修も効率的に行うことができる、というメリットがあると思います。Reduxもその類かと思われるので、頑張って習得していきたいです。

次回記事は今後更新予定。

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

日本一わかりやすいReact-Redux入門#4~#7

はじめに

この記事は、Youtubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact-Redux入門』の学習備忘録です。

前回講座で学んだ React に続き、大規模アプリ開発の必須のライブラリである Redux についても勉強をしていきます。

前回の記事はこちら

#4...Actionsを書いてstateの変更を依頼しよう

  • Actionsは、Fluxフローにおいて窓口の役割を果たす。
  • アプリから受け取った「stateの変更依頼」をReducersに渡す
  • Actionsはプレーンなオブジェクトを返す
src/Reducks/users/action.js
export const SIGN_IN = "SIGN_IN";
export const signInAction = {userState} => {
  return {
    type: "SIGN_IN",
    payload: {
      isSignedIn: true,
      uid: userState.uid,
      username: userState,username
    }
  }
};

export const SIGN_OUT = "SIGN_OUT";
export const signOutAction = () => {
  return {
    type: "SIGN_OUT",
    payload: {
      isSignedIn: false,
      uid: "",
      username: ""
    }
  }
}

5...Reducersの作り方とスプレッド構文の使い方

  • ReducersはActionsからデータを受け取り、Storeのstateをどう変更するか決める。
  • Reducersは、Storeの”現在の状態”と”初期の状態”の情報を保有する。
  • 初期状態の定義は、initialState.jsで行う。
src/reducks/store/initialState.js
const initialState = {
  users: {
    isSignedIn: false,
    uid: "",
    username: ""
  }
};

export default initialState
src/users/reducers.js
import * as Actions from './actions'
import initialState from '../store/initialState'

export const UsersReducer = (state = initialState.users, action) => {
  switch (action.type) {
    case Actions.SIGN_IN:
      return {
        ...state,
        ...action.payload
      }
      default:
        return state
  }
}

...はスプレッド構文というjavascriptの文法で、オブジェクトの展開を表す。すなわち、return文は下記と同じ意味になる・

      return {
        isSignedIn: false,
        uid: "",
        username: ""
        isSignedIn: true,
        uid: userState.uid,
        username: userState,username
      }

同じkeyのものが繰り返された時は、後半にあるものが上書きして残る。

#6...Redux(Store)とReactを接続してstateを変更しよう

  • Storeはstateを保存する役割。Reducersにより、Store内部のstateの値を変更される。
src/reducks/store/store.js
import {
  createStore as reduxCreateStore,
  combineReducers,
} from 'redux';

import {UsersReducer} from '../users/reducers';

export default function createStore() {
  return reduxCreateStore(
    combineReducers({
      users: UsersReducer,
    })
  );
}

  • combineReducers()は、複数のReducerを束ねる関数。今はUsersReducerしかないが、Reducerが増えた場合、ここの記述が複数行になる。

storeをReactアプリと接続するため、index.jsを編集する。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import createStore from './reducks/store/store';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

export const store = createStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
  • export const store = createStore();で、Storeを生成。
  • <Provider>タグで<App />をラッピングすることで、StoreをReactアプリに渡す。

本当にstoreとReactアプリが接続されたのかを、Redux Hooksを用いて確認する。

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import {useDispatch. useSelector} from "react-redux";

function App() {
  const dispatch = useDispatch()
  const selector = useSelector((state) => state)

  console.log(selector.users)

  return (
...

localhost:3000をGoogle Chromeで開き、検証ツールからconsole.log()の結果を見に行く。

image.png

initialStateとして定義しているuserオブジェクトの初期値が、Reactアプリに渡っているのが分かります!

次に、このstateの値を更新してみます。src/reducks/users/actions.jsで定義したsignInAction()が発火するボタンをApp.js内に追記します。

src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import {useDispatch, useSelector} from "react-redux";
import {signInAction} from "./reducks/users/actions"

function App() {
  const dispatch = useDispatch()
  const selector = useSelector((state) => state)

  console.log(selector.users)

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
        <button onClick={() => dispatch(signInAction({uid: "0001", username: "torahack"})) } >
          Sign In
        </button>
      </header>
    </div>
  );
}

export default App;

<button>タグのonClickイベントに、先ほどのsignInAction()を渡しています。このボタンをクリックすると、

image.png

stateの値が更新されているのが分かります!

#7...URLに応じたコンポーネントを表示しよう

connected-react-routerというReduxライブラリを使用する。これは、Reduxのstoreを利用してルーティングを管理するというもの。

store.jsindex.jsを編集する。

src/reducks/store/store/js
import {
  createStore as reduxCreateStore,
  combineReducers,
  applyMiddleware
} from 'redux';
import {connentReactRouter, routerMiddleware} from "connected-react-router";

import {UsersReducer} from '../users/reducers';

export default function createStore(history) {
  return reduxCreateStore(
    combineReducers({
      router: connentReactRouter(histroy),
      users: UsersReducer,
    }),
    applyMiddleware(
      routerMiddleware(history)
    )
  );
}
  • createStore()が新たにhistoryという引数を受け取っている。これは、サイト遷移の履歴が入っている
  • routerというstate名で、このhistoryを管理する。
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import createStore from './reducks/store/store';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import * as History from 'history';

const histroy = History.createBrowserHistory();
export const store = createStore(history);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <App />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
  • <ConnectedRouter>でさらに<App />をラッピング。

具体的なルーティングの定義を実装する、実装は、Router.jsxという特殊なコンポーネントファイルを作成して行う。

src/Router.jsx
import React from 'react';
import {Route, Switch} from "react-router";
import {Login, Home} from "templates";

const Router = () => {
  return (
    <Switch>
      <Route exact path="/login" component={Login} />
      <Route exact path="(/)?" component={Home} />
    </Switch>
  );
};

export default Router
  • <Route>タグ内で、パスとコンポーネントの対応を記述する。
  • exact path=が完全一致、path=が部分一致。静的なルーティングに対しては前者を使い、動的なルーティングに対しては後者を使う("/posts/:id"のようなケース)

この時点では、templatesファイルは未作成。これを作る前に、App.jsを変更する。拡張子をApp.jsxにした上で、中身をがらっと変える。

src/App.jsx
import React from 'react'
import Router from './Router'

const App = () => {
  <main>
    <Router />
  </main>
};

export default App;

<main>タグでRouterコンポーネントを呼び出している。この<Router />の中身が、パスごとに切り替わるイメージ

続いて、templatesファイルを作る。templatesファイルは、静的ページにつき1個ずつ存在し、そのページ(ルーティング)において親コンポーネントの役割を果たす。

作成するファイルは以下の通り。
- src/templates/Login.jsx
- src/templates/Home.jsx
- src/templates/index.js

index.jsはエントリーポイントの役割を果たす。

src/templates/Login.jsx
import React from 'react';
import {useDispatch} from "react-redux";
import {push} from "connected-react-router";

const Login = () => {
  const dispatch = useDispatch();
  return (
    <div>
      <h2>ログイン</h2>
      <button onClick={() => dispatch(push('/'))}>
        ログインする
      </button>
    </div>
  );
};

export default Login
  • useDispatch()で store に紐付いた dispatch が取得できる。
  • push()を実行すると、引数のパスにページを遷移させることができる。現時点では、ダミーとしてルートパスを指定。
src/templates/Home.jsx
import React from 'react';

const Home = () => {
  return (
    <h2>Home</h2>
  );
};

export default Home
src/templates/index.js
export {default as Login} from './Login'
export {default as Home} from './Home'

ブラウザで確認すると、

path = "/" path = "/login"
image.png image.png

上手く表示されています!また、ログインボタンを押すことで、Home画面へ遷移できるようになっています。

おわり

今回はここまで。

第三回講座で概念を学んだFluxフローについて、実際に手を動かすことでなんとなーく分かってきた気がしてきました。

開発フレームワークにおける「どこに何を書くべきかが決まっている」という状態は、初期の学習コストは高くなりがちな反面、慣れることさえできれば、開発だけでなく保守・改修も効率的に行うことができる、というメリットがあると思います。Reduxもその類かと思われるので、頑張って習得していきたいです。

次回記事は今後更新予定。

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

How to Install React JS in Laravel 7 with Bootstrap

If you want to build a React application with Laravel, then the first and foremost thing is you must know how to install React in Laravel 7. We will use laravel/ui Package to install react in laravel with Bootstrap 4.

click here to read more:
https://www.positronx.io/how-to-install-react-js-in-laravel-with-bootstrap/

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

SPA(React) + Firebase Authentication でリロード時もログイン情報を維持できるようにする

ちょっとハマったので備忘録
ここで紹介する方法以外でベストプラクティス等あればコメントで教えていただけると嬉しいです

Firebase Authentication + SPA でよくある設計

Firebase Authentication を使ったログイン機能付き SPA の実装例をググったときに見かける設計として

「認証が必要なページにアクセスされたときは、ログインしていればそのまま表示し、ログインしていなければログインページへ飛ばす」

というのがよくあるパターンだと思います。例えば React Router を使うと次のような実装が考えられます。 
※ React Router ドキュメントのコードをベースにしています https://reactrouter.com/web/example/auth-workflow

const PrivateRoute: React.FC<RouteProps> = ({ children, ...rest }) => {
  // getUser は保持されたユーザー情報を取得するための何らかの関数です。実装は省略
  // 後で登場する setUser 関数で保持したユーザー情報を取り出します
  // state を使ってもいいし、Redux 等のストアを使ってもいいし、Context を使っても構いません
  const user = getUser();

  return (
    <Route
      {...rest}
      render={({ location }) => {
        if (user) {
            return children;
        } else {
            return <Redirect to={{ pathname: 'login', state: { from: location } }} />;
        }
      }}
    />
  );
};

export default function App() {
  useEffect(() => {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        // setUser はユーザー情報を保持しておくための何らかの関数です。実装は省略
        // state を使ってもいいし、Redux 等のストアを使ってもいいし、Context を使っても構いません
        setUser(user);
      }
    });
  }, []);

  return (
    <>
      <Router>
        <Link to="/public">ログインが不要なページ</Link>
        <Link to="/private">ログインが必要なページ</Link>
        <Switch>
      <Route path="/login">
            <>
              <div>ログインページです</div>
              {/* Firebase Authentication へのログイン方法は何でも OK ですが */}
              {/* ここでは StyledFirebaseAuth を使ってログイン画面を表示します  */}
              <StyledFirebaseAuth ... />
            </>
          </Route>
          <Route path="/public">
            <div>このページは誰でも見ることができます</div>
          </Route>
          <PrivateRoute path="/private">
            <div>このページはログインしたユーザーだけが見ることができます</div>
          </PrivateRoute>
        </Switch>
      </Router>
    </>
  );
}

この実装で基本的には問題ないです。トップページ / から /public に遷移するとログインの有無に関係なくページが表示され、 /private に遷移するとログインしていれば表示されるし、ログインしていなければログインページへリダイレクトされます

問題点

ページ遷移でアプリケーション内をぐるぐるする分には問題ないのですが、 /private にいる状態でページをリロードしてみると先程ログインしたのにまたログインページへ飛ばされてしまいます
Firebase Authentication 的にはログイン状態ではあるのですが、 firebase.auth().onAuthStateChanged は非同期で走るので、 /private にダイレクトにアクセスすると setUser がされる前に getUser が実行されてしまいます
setUser されてないのでユーザー情報は存在せず、ログインされていない判定になりログインページへ再び飛ばされてしまいます

解決方法

実装を工夫してあげる必要があります
解決方法はシンプルで onAuthStateChanged が終わってからページを表示するかログインページへ飛ばすかどうかを確定させるアプローチで対処できます
(ここでは React Router を使った場合の解決方法を書いていますが、他のルーターの場合でもミドルウェアを使って似たようなことができると思います)

PrivateRoute を次のように書き換えてあげます

const PrivateRoute: React.FC<RouteProps> = ({ children, ...rest }) => {
  const [authChecked, setAuthChecked] = useState(false);

  useEffect(() => {
    firebaseAuth.onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      }

      setAuthChecked(true);
    });
  }, []);

  const user = getUser();

  return (
    <Route
      {...rest}
      render={({ location }) => {
        if (authChecked) {
          if (user) {
            return children;
          } else {
            return <Redirect to={{ pathname: 'login', state: { from: location } }} />;
          }
        } else {
          return <></>;
        }
      }}
    />
  );
};

「ログイン状態の確認が終わったか」「ログインしているかどうか」の2段階で分岐することで UX を損なうことなくリロード問題に対処できるようになりました

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

僕が考えた最強の React アーキテクチャ

Untitled Diagram-Page-1.png

Component

Page

Container と Component を組み合わせてページを構成する

Container, Component

Container → Container Components
Component → Presentational Components

redux.js.org_basics_usage-with-react.png

https://redux.js.org/basics/usage-with-react#presentational-and-container-components

Store

Action, Reducer, State, Selector

vuex.png

https://vuex.vuejs.org/ja/

Facade

https://ja.wikipedia.org/wiki/Facade_パターン

Service

CRUD

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

[React / material-ui] install方法メモ ~古い方法でインストールしないように...~

概要

material-uiのインストール方法を調べていたら、2018年の本と、最新のインストール方法では違うことに気が付いたのでメモしておく。

なお、古い方法でインストールしたら当然古いパッケージがインストールされ、最新の機能や書き方が利用できないので、随時新しいものを使用したい。

2018年の本(いくつか)のインストール方法

$ npm install material-ui

最新のインストール方法(2020/07/05時点)

$ npm install @material-ui/core

思ったこと

「古い本なんぞに頼ってんじゃねぇ!!」ということなんでしょう...(´・_・`)ほよょ

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