Laravel
React

Laravelの認証画面をReactで全とっかえ

やろうやろうと思いつつ中々実現できなかったヤツ

やっと大体できたので投稿

この構造がベストかと言えばそうでもないような気がするけど

やってみるといいReduxの練習になったと思う

Email Verificationと多言語はまだ未対応

追記:Email Verificationについては以下を参考にした

Laravel:Email Verification(メールアドレスの確認)を使う

無駄にClass Componentになってるものはおいおい修正していきます


準備

プロジェクトの準備とか

DBの準備とかメールサーバーとかの.envの設定は省略

React関連のパッケージのインストールとデフォルトが古いのでアップデートしておく

$ composer create-project --prefer-dist laravel/laravel LaravelReact

$ cd LaravelReact
$ php artisan preset react
$ npm install --save-dev react react-dom connected-react-router history prop-types
$ npm install --save-dev react-bootstrap@next react-redux react-router react-router-dom
$ npm install --save-dev redux redux-persist @babel/preset-react axios bootstrap laravel-mix


PHP側の追加


ルートの追加

Auth::routes()で認証関連の処理が登録されるので

後から画面系のルートを名前付きで上書き

ワイルドカード受付は一番最後に書く


app/User.php

-use Illuminate\Contracts\Auth\MustVerifyEmail;

+use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract;
+use Illuminate\Auth\MustVerifyEmail;

-class User extends Authenticatable
+class User extends Authenticatable implements MustVerifyEmailContract
{
- use Notifiable;
+ use MustVerifyEmail, Notifiable;


routes/web.php

Route::get('/', function(){

return view('welcome');
});

+Auth::routes(['verify' => true]);

+Route::get('/login', 'ReactController@index')->name('login');
+Route::get('/register', 'ReactController@index')->name('register');
+Route::get('password/reset', 'ReactController@index')->name('password.request');
+Route::get('password/reset/{token}', 'ReactController@index')->name('password.reset');
+Route::get('email/verify', 'ReactController@index')->name('verification.notice');

+Route::post('/session', 'ReactController@session')->name('session');
+Route::get('/{router}', 'ReactController@index')->name('home');



コントローラーの追加

画面表示用のindexとログインセッション判定用のsessionを定義


app/Http/Controllers/ReactController.php

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ReactController extends Controller
{
public function index(Request $request)
{
return view('react');
}

public function session(Request $request)
{
return $request->user();
}
}



テンプレートの追加

Laravelのステージ情報をlaravelStatus、エラー情報をlaravelErrorsとしてグローバル定義


resources/views/react.blade.php

<!DOCTYPE html>

<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">

<title>{{ config('app.name', 'Laravel') }}</title>

<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">

<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="react-root"></div>
<script>
var laravelSession = {};
laravelSession['status']=@if(session('status'))'{{session('status')}}'@else''@endif;
laravelSession['resent']=@if(session('resent'))'{{session('resent')}}'@else''@endif;
var laravelErrors=@php print(htmlspecialchars_decode($errors))@endphp;
</script>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
</body>
</html>



JS側の追加


構成概要

app.js

bootstrap.js

ReactRoot.js

stores



antions



reducers



routes



components

┗Menus

┗Forms

┗Elements


外枠の定義

以前の記事ReactのClass ComponentでReact-Reduxに繋ぐにて作った構成を元に配備

一旦ログインセッションを問い合わせてからその結果を持ってからレンダリング処理へ

bootstrap.jsは既存のままで利用


resources/js/app.js

require('./bootstrap');

// ↓以下を追記

import { Provider } from 'react-redux'
import React from 'react'
import ReactDOM from 'react-dom'
import ReactRoot from './ReactRoot'
import configureStore, { history } from './stores/configureStore'
import { persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'

const store = configureStore()

const pstore = persistStore(store)

const render = (props) => {
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={<p>loading...</p>} persistor={pstore}>
<ReactRoot history={history} responseSession={props} />
</PersistGate>
</Provider>,
document.getElementById('react-root')
)
}

function authSession()
{
let params = new URLSearchParams();
let url = '/session';
window.axios.post(url,params)
.then((response)=>{
render(response.data)
})
}

authSession()


フォーム要素に対しエラーがあれば再描画エラーがなければリロードとしてフォーム要素をクリアするためReduxアクションを呼び出し

適用遅延?があるようなのでコンストラクタで設定したredux状態ではなく上のコンポーネントから受け取ったログイン状態を

ルート振り分けに渡す


resources/js/ReactRoot.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import {
setSession,
setName,
setEmail,
setRemember,
setCSRF
} from './actions/authentications'
import routes from './routes'

class ReactRoot extends React.Component {
constructor(props){
super(props)
props.setSession(props.responseSession)
if(laravelErrors['name']===undefined
&&laravelErrors['email']===undefined
&&laravelErrors['password']===undefined)
{
this.props.setName('')
this.props.setEmail('')
this.props.setRemember(false)
}
let token = document.head.querySelector('meta[name="csrf-token"]')
this.props.setCSRF(token.content)
}
render(){
return (
<ConnectedRouter history={this.props.history}>
{ routes(this.props.responseSession) }
</ConnectedRouter>
)
}
}

ReactRoot.propTypes = {
history: PropTypes.object,
csrf: PropTypes.string,
setSession: PropTypes.func.isRequired,
setName: PropTypes.func.isRequired,
setEmail: PropTypes.func.isRequired,
setRemember: PropTypes.func.isRequired,

}

const mapStateToProps = state => ({
csrf: state.csrf,
})

const mapDispatchToProps = dispatch => ({
setCSRF: (csrf) => dispatch(setCSRF(csrf)),
setSession: (session) => dispatch(setSession(session)),
setName: (name) => dispatch(setName(name)),
setEmail: (email) => dispatch(setEmail(email)),
setRemember: (remember) => dispatch(setRemember(remember)),
})

export default connect(mapStateToProps, mapDispatchToProps)(ReactRoot)



Store

ここも前回の記事参照


resources/js/stores/configureStore.js

import { createBrowserHistory } from 'history'

import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from '../reducers'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'

const persistConfig = {
key: 'root',
storage,
blacklist: ['router'],
}

export const history = createBrowserHistory()

const persistedReducer = persistReducer(persistConfig, createRootReducer(history))

export default function configureStore(preloadedState) {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
persistedReducer,
preloadedState,
composeEnhancer(
applyMiddleware(
routerMiddleware(history),
),
),
)
return store
}



Action

ログインセッション、フォーム利用要素(名前、メール、記憶チェック)、CSRFトークン、パスワードリセット時に使われるリクエストトークンをReduxで管理する


resources/js/actions/authentications.js

export const setSession = (session) => ({

type: 'SET_SESSION',
payload: {
id:session.id,
name:session.name
},
})

export const setName = (name) => ({
type: 'SET_NAME',
payload: {
name
}
})

export const setEmail = (email) => ({
type: 'SET_EMAIL',
payload: {
email
}
})

export const setRemember = (remember) => ({
type: 'SET_REMEMBER',
payload: {
remember
}
})

export const setCSRF = (csrf) => ({
type: 'SET_CSRF',
payload: {
csrf
}
})

export const setPrams = (request) => ({
type: 'SET_PARAMS',
payload: {
request
}
})



Reducer

認証関連のReducerは...authenticationsで展開


resources/js/reducers/index.js

import { combineReducers } from 'redux'

import { connectRouter } from 'connected-react-router'
import * as authentications from './authentications'

const rootReducer = (history) => combineReducers({
...authentications,
router: connectRouter(history)
})

export default rootReducer


...authenticationsで展開するためreducer名は状態名をそのまま使用


resources/js/reducers/authentications.js

export const session = (state = {auth:false,name:null}, action) => {

switch (action.type) {
case 'SET_SESSION':
let session = (action.payload.id===undefined)
? ({auth:false,name:null})
: ({auth:true,name:action.payload.name})
return session
default:
return state
}
}

export const name = (state = null, action) => {
switch (action.type) {
case 'SET_NAME':
return action.payload.name;
default:
return state
}
}

export const email = (state = null, action) => {
switch (action.type) {
case 'SET_EMAIL':
return action.payload.email;
default:
return state
}
}

export const remember = (state = false, action) => {
switch (action.type) {
case 'SET_REMEMBER':
return action.payload.remember;
default:
return state
}
}

export const csrf = (state = '', action) => {
switch (action.type) {
case 'SET_CSRF':
return action.payload.csrf;
default:
return state
}
}

export const params = (state = '', action) => {
switch (action.type) {
case 'SET_PARAMS':
return action.payload.request;
default:
return state
}
}



Route

認証済みか否かでコンテンツを切り分け

状態に適しないページはリダイレクトでデフォルトへ移動させる


resources/js/routes/index.js

import React from 'react'

import { Route, Switch, Redirect } from 'react-router'
import NavBar from '../components/NavBar'
import CardTemplate from '../components/CardTemplate'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { setSession } from '../actions/authentications'

const routes = (session)=> {
return(
<>
<NavBar />
<div className="py-3">
{(()=>{
if(session.id===undefined){
return (
<Switch>
<Route exact path="/login" render={() => <CardTemplate title="Login" content="LoginForm" />} />
<Route exact path="/register" render={() => <CardTemplate title="Register" content="RegisterForm" />} />
<Route exact path="/password/reset" render={() => <CardTemplate title="Reset Password" content="EMailForm" />} />
<Route path="/password/reset/:id" render={(props) => <CardTemplate title="Reset Password" content="ResetForm" params={props.match.params} />} />
<Redirect to="/login" />
</Switch>
)
}
else
{
if(session.email_verified_at===null)
{
return (
<Switch>
<Route exact path="/email/verify" render={() => <CardTemplate title="Verify Your Email Address" content="Verify" />} />
<Redirect to="/email/verify" />
</Switch>
)
}
else
{
return (
<Switch>
<Route exact path="/" render={() => <CardTemplate title="Welcome" content="Welcome" />} />
<Route path="/home" render={() => <CardTemplate title="Dashboard" content="Home" />} />
<Redirect to="/home" />
</Switch>
)
}
}
})()}
</div>
</>
)
}

export default routes



Component


メニュー部分

認証状態によって振り分けるためReduxから認証状態を受け取る


resources/js/components/NavBar.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import {
Container,
Navbar, Nav,
} from 'react-bootstrap'
import LeftMenu from './Menus/LeftMenu'
import RightMenu from './Menus/RightMenu'

const NavBar = (props) => (
<Navbar expand="md" bg="light" className="navbar-laravel">
<Container>
<Navbar.Brand href="/">
Laravel
</Navbar.Brand>
<Navbar.Toggle data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" />
<Navbar.Collapse id="navbarSupportedContent">
<Nav className="mr-auto">
<LeftMenu auth={props.auth} />
</Nav>
<Nav className="ml-auto">
<RightMenu auth={props.auth} />
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
)

NavBar.propTypes = {
auth: PropTypes.bool.isRequired,
}

const mapStateToProps = state => ({
auth: state.session.auth,
})

const mapDispatchToProps = dispatch => ({
})

export default connect(mapStateToProps, mapDispatchToProps)(NavBar)


状態によって表示メニューを切り替え


resources/js/components/Menus/LeftMenu.js

import React from 'react'

import LeftMemberMenu from './LeftMemberMenu'
import LeftGuestMenu from './LeftGuestMenu'

const LeftMenu = (props) => {
if(props.auth)
{
return <LeftMemberMenu />
}
else
{
return <LeftGuestMenu />
}
}

export default LeftMenu


非認証時のメニュー


resources/js/components/Menus/LeftGuestMenu.js

import React from 'react'

const LeftGuestMenu = () => (
<>
</>
)


認証時のメニュー


resources/js/components/Menus/LeftMemberMenu.js

import React from 'react'

import { Nav } from 'react-bootstrap'
import { Link } from 'react-router-dom'

const LeftMemberMenu = () => (
<>
<Nav.Item>
<Link to="/home" className="nav-link">Home</Link>
</Nav.Item >
</>
)


状態によって表示メニューを切り替え


resources/js/components/Menus/RightMenu.js

import React from 'react'

import { Link } from 'react-router-dom'
import {
Nav, NavDropdown,
Form,
} from 'react-bootstrap'
import RightMemberMenu from './RightMemberMenu'
import RightGuestMenu from './RightGuestMenu'

const RightMenu = (props) => {
if(props.auth)
{
return <RightMemberMenu />
}
else
{
return <RightGuestMenu />
}
}
export default RightMenu


非認証時のメニュー


resources/js/components/Menus/RightGuestMenu.js

import React from 'react'

import { Link } from 'react-router-dom'
import { Nav } from 'react-bootstrap'

const RightGuestMenu = () => (
<>
<Nav.Item>
<Link to="/login" className="nav-link">Login</Link>
</Nav.Item>
<Nav.Item>
<Link to="/register" className="nav-link">Register</Link>
</Nav.Item>
</>
)
export default RightGuestMenu


認証時のメニュー

表示するログインユーザー名をReduxから取得する


resources/js/components/Menus/RightMemberMenu.js

import React from 'react'

import { Link } from 'react-router-dom'
import { NavDropdown, Form } from 'react-bootstrap'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import CSRFToken from '../Elements/CSRFToken'

class RightMemberMenu extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<NavDropdown title={this.props.name+' 様'}>
<NavDropdown.Item href="/logout" onClick={this.preSubmit}>Logout</NavDropdown.Item>
<Form id="logout-form" action="/logout" method="POST" style={{display:'none'}}>
<CSRFToken />
</Form>
</NavDropdown>
)
}
preSubmit(currentEvent){
currentEvent.preventDefault();
document.getElementById('logout-form').submit();
}
}
RightMemberMenu.propTypes = {
name: PropTypes.string,
}
const mapStateToProps = state => ({
name: state.session.name,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(RightMemberMenu)



コンテンツ部分

メインコンテンツのカード部分のテンプレート


resources/js/components/CardTemplate.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Container, Row, Col, Card } from 'react-bootstrap'
import ContentSelector from './ContentSelector'
import { setPrams } from '../actions/authentications'

class CardTemplate extends React.Component {
constructor(props){
super(props)
if(props.params!==undefined)
{
props.setPrams(props.params)
}
}
render(){
return (
<Container>
<Row className="justify-content-center">
<Col md="8">
<Card>
<Card.Header>{this.props.title}</Card.Header>
<Card.Body>
<ContentSelector content={this.props.content} />
</Card.Body>
</Card>
</Col>
</Row>
</Container>
)
}
}
CardTemplate.propTypes = {
setPrams: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
})
const mapDispatchToProps = dispatch => ({
setPrams: (request) => dispatch(setPrams(request)),
})
export default connect(mapStateToProps, mapDispatchToProps)(CardTemplate)


ReactRouterから来たパスによって画面振り分け


resources/js/components/ContentSelector.js

import React from 'react'

import LoginForm from './Forms/LoginForm'
import RegisterForm from './Forms/RegisterForm'
import EMailForm from './Forms/EMailForm'
import ResetForm from './Forms/ResetForm'
import Home from './Forms/Home'
import Verify from './Forms/Verify'

const ContentSelector = (props) => {
switch(props.content){
case 'LoginForm':
return <LoginForm />
case 'RegisterForm':
return <RegisterForm />
case 'EMailForm':
return <EMailForm />
case 'ResetForm':
return <ResetForm />
case 'Home':
return <Home />
case 'Verify':
return <Verify />
default:
return <div>UnMatch</div>
}
}
export default ContentSelector


ログインフォーム


resources/js/components/Forms/LoginForm.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import CheckInput from '../Elements/CheckInput'
import LoginButton from '../Elements/LoginButton'
import CSRFToken from '../Elements/CSRFToken'
import { setEmail, setRemember } from '../../actions/authentications'

class LoginForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<Form method="POST" action="/login" id="login-form">
<CSRFToken />
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={true}
/>
<TextInput
identity="password"
controlType="password"
autoComplete="current-password"
label="Password"
/>
<CheckInput
identity="remember"
label="Remember Me"
checked={this.props.remember}
action={this.props.setRemember}
/>
<LoginButton />
</Form>
)
}
}
LoginForm.propTypes = {
email: PropTypes.string,
remember: PropTypes.bool,
setEmail: PropTypes.func.isRequired,
setRemember: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
email: state.email,
remember: state.remember,
})
const mapDispatchToProps = dispatch => ({
setEmail: (email) => dispatch(setEmail(email)),
setRemember: (remember) => dispatch(setRemember(remember)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)


新規登録フォーム


resources/js/components/Forms/RegisterForm.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import { setName, setEmail } from '../../actions/authentications'

class LoginForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<Form method="POST" action="/register" id="login-form">
<CSRFToken />
<TextInput
identity="name"
controlType="text"
label="Name"
defaultValue={this.props.name}
action={this.props.setName}
autoFocus={true}
/>
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={false}
/>
<TextInput
identity="password"
controlType="password"
autoComplete="new-password"
label="Password"
/>
<TextInput
identity="password-confirm"
controlType="password"
name="password_confirmation"
autoComplete="new-password"
label="Confirm Password"
/>
<SubmitButton label="Register" />
</Form>
)
}
}
LoginForm.propTypes = {
name: PropTypes.string,
email: PropTypes.string,
setName: PropTypes.func.isRequired,
setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
name: state.name,
email: state.email,
})
const mapDispatchToProps = dispatch => ({
setName: (name) => dispatch(setName(name)),
setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)


パスワードリセット申請フォーム


resources/js/components/Forms/EMailForm.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import SessionAlert from '../Elements/SessionAlert '
import { setEmail } from '../../actions/authentications'

class ResetForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<>
<SessionAlert target="status" />
<Form method="POST" action="/password/email" id="login-form">
<CSRFToken />
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={true}
/>
<SubmitButton label="Send Password Reset Link" />
</Form>
</>
)
}
}
ResetForm.propTypes = {
email: PropTypes.string,
setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
email: state.email,
})
const mapDispatchToProps = dispatch => ({
setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(ResetForm)


パスワードリセットフォーム


resources/js/components/Forms/ResetForm.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import RequestToken from '../Elements/RequestToken'
import SessionAlert from '../Elements/SessionAlert '
import { setEmail } from '../../actions/authentications'

class ResetForm extends React.Component {
constructor(props) {
super(props)
}
render(){
return (
<>
<SessionAlert target="status" />
<Form method="POST" action="/password/reset" id="login-form">
<CSRFToken />
<RequestToken />
<TextInput
identity="email"
controlType="email"
label="E-Mail Address"
defaultValue={this.props.email}
action={this.props.setEmail}
autoFocus={true}
/>
<TextInput
identity="password"
controlType="password"
autoComplete="new-password"
label="Password"
/>
<TextInput
identity="password-confirm"
controlType="password"
name="password_confirmation"
autoComplete="new-password"
label="Confirm Password"
/>
<SubmitButton label="Reset Password" />
</Form>
</>
)
}
}
ResetForm.propTypes = {
email: PropTypes.string,
setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
email: state.email,
})
const mapDispatchToProps = dispatch => ({
setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(ResetForm)


ログイン後のフォーム画面

ステータスセッションがあればば表示する(パスワードリセット時に見られる)


resources/js/components/Forms/Home.js

import React from 'react'

import { Container, Row, Col, Card } from 'react-bootstrap'
import StatusAlert from '../Elements/StatusAlert'

const Home = () => (
<>
<SessionAlert target="status" />
You are logged in!
</>
)
export default Home


メール認証前の画面

resentセッションがあればば表示する(メールの再送信に成功した時に見られる)


resources/js/components/Forms/Verify.js

import React from 'react'

import { Container, Row, Col, Card } from 'react-bootstrap'
import SessionAlert from '../Elements/SessionAlert'

const Verify = () => (
<>
<SessionAlert target="resent" />
Before proceeding, please check your email for a verification link.
If you did not receive the email, <a href="/email/resend">click here to request another</a>.
</>
)

export default Verify



汎用部品

ReduxからCSRFトークンを取得したinput要素


resources/js/components/Elements/CSRFToken.js

import React from 'react'

import { Form } from 'react-bootstrap'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'

const CSRFToken = (props) => (
<Form.Control type="hidden" name="_token" defaultValue={props.csrf} />
)
CSRFToken.propTypes = {
csrf: PropTypes.string,
}
const mapStateToProps = state => ({
csrf: state.csrf,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(CSRFToken)


パスワードリセット時のリクエストトークンを取得したinput要素


resources/js/components/Elements/RequestToken.js

import React from 'react'

import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'

const RequestToken = (props) => (
<Form.Control type="hidden" name="token" defaultValue={props.params.id} />
)
RequestToken.propTypes = {
params: PropTypes.object,
}
const mapStateToProps = state => ({
params: state.params,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(RequestToken)


セッションに通知情報があればをグローバル変数で取得してあればアラート表示


resources/js/components/Elements/SessionAlert.js

import React from 'react'

import { Alert } from 'react-bootstrap'

const SessionAlert = (props) => {
if(laravelSession[props.target]!=='')
{
return (
<Alert variant="success" role="alert">
{
props.target==="resent"
?'A fresh verification link has been sent to your email address.'
:laravelSession[props.target]
}
</Alert>
)
}
else
{
return <></>
}
}

export default SessionAlert


エラー情報をグローバル変数で取得してあればエラーメッセージ表示


resources/js/components/Elements/FormError.js

import React from 'react'

const FormError = (props) => (
<span className="invalid-feedback" role="alert">
<strong>{props.message}</strong>
</span>
)
export default FormError


テキスト系input要素とラベル、エラーメッセージを組み合わせた部品


resources/js/components/Elements/TextInput.js

import React from 'react'

import { Row, Col, Form } from 'react-bootstrap'
import FormError from './FormError'

class TextInput extends React.Component {
constructor(props){
super(props)
this.state={
error:'',
isInvalid:'',
};
if(laravelErrors[this.props.identity]!==undefined)
{
this.state.error=laravelErrors[this.props.identity][0]
this.state.isInvalid=' is-invalid'
}
}
render(){
let inputProps = {}
inputProps.id=this.props.identity
inputProps.type=this.props.controlType===undefined?'text':this.props.controlType
inputProps.className=this.state.isInvalid
inputProps.name=this.props.name===undefined?this.props.identity:this.props.name
inputProps.autoComplete=this.props.autoComplete===undefined?this.props.identity:this.props.autoComplete
inputProps.required=this.props.required===undefined?true:this.props.required
inputProps.autoFocus=this.props.autoFocus===undefined?false:this.props.autoFocus
if(this.props.controlType!=='password')
{
inputProps.defaultValue=this.props.defaultValue
inputProps.onChange=(text)=>{this.props.action(text.target.value)}
}
return (
<Form.Group>
<Row>
<Form.Label htmlFor={this.props.identity} className="col-md-4 col-form-label text-md-right">{this.props.label}</Form.Label>
<Col md="6">
<Form.Control {...inputProps} />
<FormError message={this.state.error} />
</Col>
</Row>
</Form.Group>
)
}
}

export default TextInput


セッションを記憶ボタンから汎用化したチェックボックス部品


resources/js/components/Elements/CheckInput.js

import React from 'react'

import { Row, Col, Form } from 'react-bootstrap'
import FormError from './FormError'

const CheckInput = (props) => (
<Form.Group>
<Row>
<Col md="6" className="offset-md-4">
<div className="form-check">
<Form.Check
id={props.identity}
type="checkbox"
className="form-check-input"
name={props.identity}
checked={props.checked}
onChange={ check => props.action(check.target.checked) }
/>
<Form.Label className="form-check-label" htmlFor={props.identity}>
{props.label}
</Form.Label>
</div>
</Col>
</Row>
</Form.Group>
)
export default CheckInput


各種送信ボタン部品


resources/js/components/Elements/SubmitButton.js

import React from 'react'

import { Row, Col, Button } from 'react-bootstrap'

const SubmitButton = (props) => (
<Row className="form-group mb-0">
<Col md="6" className="offset-md-4">
<Button type="submit" variant="primary">
{props.label}
</Button>
</Col>
</Row>
)
export default SubmitButton


ログインボタンだけパスワード忘れのリンクが付いているので別定義


resources/js/components/Elements/LoginButton.js

import React from 'react'

import { Row, Col, Button } from 'react-bootstrap'
import { Link } from 'react-router-dom'

const LoginButton = () => (
<Row className="form-group mb-0">
<Col md="8" className="offset-md-4">
<Button type="submit" variant="primary">
Login
</Button>
<Link to="/password/reset" className="btn btn-link">
Forgot Your Password?
</Link>
</Col>
</Row>
)
export default LoginButton