19
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-04-21

やろうやろうと思いつつ中々実現できなかったヤツ
やっと大体できたので投稿
この構造がベストかと言えばそうでもないような気がするけど
やってみるといい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
19
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?